diff --git a/CHANGELOG.md b/CHANGELOG.md index 98afc6589c..6eeef2699a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Waypoints saved between sessions and shared with group members. +- New rocks ### Changed diff --git a/assets/world/features.ron b/assets/world/features.ron index 25a5005a61..57903371eb 100644 --- a/assets/world/features.ron +++ b/assets/world/features.ron @@ -4,6 +4,7 @@ ( caverns: false, // TODO: Disabled by default until cave overhaul caves: true, + rocks: true, shrubs: true, trees: true, scatter: true, diff --git a/world/src/all.rs b/world/src/all.rs index 3d70c3bce3..6f28ce0e9a 100644 --- a/world/src/all.rs +++ b/world/src/all.rs @@ -1,7 +1,7 @@ use crate::util::math::close; use enum_iterator::IntoEnumIterator; use std::ops::Range; -use vek::*; +use vek::Vec2; #[derive(Copy, Clone, Debug, IntoEnumIterator)] pub enum ForestKind { @@ -113,6 +113,8 @@ impl ForestKind { } } +/// Not currently used with trees generated by the tree layer, needs to be +/// reworked pub struct TreeAttr { pub pos: Vec2, pub seed: u32, diff --git a/world/src/block/mod.rs b/world/src/block/mod.rs index 40175d646b..621b53c89e 100644 --- a/world/src/block/mod.rs +++ b/world/src/block/mod.rs @@ -77,7 +77,7 @@ impl<'a> BlockGen<'a> { marble: _, marble_mid: _, marble_small: _, - rock, + rock_density: _, // temp, // humidity, stone_col, @@ -183,28 +183,6 @@ impl<'a> BlockGen<'a> { } else { None } - .or_else(|| { - // Rocks - if (height + 2.5 - wposf.z as f32).div(7.5).abs().powi(2) < rock { - let field0 = RandomField::new(world.seed + 0); - let field1 = RandomField::new(world.seed + 1); - let field2 = RandomField::new(world.seed + 2); - - Some(Block::new( - BlockKind::WeakRock, - stone_col.map2( - Rgb::new( - field0.get(wpos) as u8 % 16, - field1.get(wpos) as u8 % 16, - field2.get(wpos) as u8 % 16, - ), - |stone, x| stone.saturating_sub(x), - ), - )) - } else { - None - } - }) .or_else(|| { let over_water = height < water_height; // Water @@ -232,11 +210,9 @@ impl<'a> ZCache<'a> { - self.sample.cliff_offset.max(0.0); let min = min - 4.0; - let rocks = if self.sample.rock > 0.0 { 12.0 } else { 0.0 }; - let warp = self.sample.chaos * 32.0; - let ground_max = self.sample.alt + warp + rocks + 2.0; + let ground_max = self.sample.alt + warp + 2.0; let max = ground_max.max(self.sample.water_level + 2.0 + self.sample.ice_depth); diff --git a/world/src/column/mod.rs b/world/src/column/mod.rs index bace8d5d1d..3fb174d264 100644 --- a/world/src/column/mod.rs +++ b/world/src/column/mod.rs @@ -841,17 +841,8 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { let alt = alt + riverless_alt_delta; let basement = alt + sim.get_interpolated_monotone(wpos, |chunk| chunk.basement.sub(chunk.alt))?; - - 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(water_dist.map(|wd| (wd / 2.0).clamped(0.0, 1.0).sqrt()).unwrap_or(1.0)) - .mul(rockiness) - .sub(0.4) - .max(0.0) - .mul(8.0); + // Adjust this to make rock placement better + let rock_density = rockiness; // Columns near water have a more stable temperature and so get pushed towards // the average (0) @@ -1182,7 +1173,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { marble, marble_mid, marble_small, - rock, + rock_density, temp, humidity, spawn_rate, @@ -1217,7 +1208,7 @@ pub struct ColumnSample<'a> { pub marble: f32, pub marble_mid: f32, pub marble_small: f32, - pub rock: f32, + pub rock_density: f32, pub temp: f32, pub humidity: f32, pub spawn_rate: f32, diff --git a/world/src/config.rs b/world/src/config.rs index fddc417e04..d81427928b 100644 --- a/world/src/config.rs +++ b/world/src/config.rs @@ -81,6 +81,7 @@ pub const CONFIG: Config = Config { pub struct Features { pub caverns: bool, pub caves: bool, + pub rocks: bool, pub shrubs: bool, pub trees: bool, pub scatter: bool, diff --git a/world/src/layer/mod.rs b/world/src/layer/mod.rs index 220248dff0..e24c14b8aa 100644 --- a/world/src/layer/mod.rs +++ b/world/src/layer/mod.rs @@ -1,3 +1,4 @@ +pub mod rock; pub mod scatter; pub mod shrub; pub mod spot; @@ -5,7 +6,8 @@ pub mod tree; pub mod wildlife; pub use self::{ - scatter::apply_scatter_to, shrub::apply_shrubs_to, spot::apply_spots_to, tree::apply_trees_to, + rock::apply_rocks_to, scatter::apply_scatter_to, shrub::apply_shrubs_to, spot::apply_spots_to, + tree::apply_trees_to, }; use crate::{ diff --git a/world/src/layer/rock.rs b/world/src/layer/rock.rs new file mode 100644 index 0000000000..a22342f7ad --- /dev/null +++ b/world/src/layer/rock.rs @@ -0,0 +1,348 @@ +use crate::{ + util::{ + gen_cache::StructureGenCache, seed_expan, RandomField, Sampler, StructureGen2d, + UnitChooser, NEIGHBORS, NEIGHBORS3, + }, + Canvas, ColumnSample, CONFIG, +}; +use common::terrain::{Block, BlockKind}; +use ordered_float::NotNan; +use rand::prelude::*; +use rand_chacha::ChaChaRng; +use vek::*; + +struct Rock { + wpos: Vec3, + seed: u32, + units: Vec2>, + kind: RockKind, +} + +pub fn apply_rocks_to(canvas: &mut Canvas, _dynamic_rng: &mut impl Rng) { + let mut rock_gen = StructureGenCache::new(StructureGen2d::new(canvas.index().seed, 24, 10)); + + let info = canvas.info(); + canvas.foreach_col(|canvas, wpos2d, col| { + let rocks = rock_gen.get(wpos2d, |wpos, seed| { + let col = info.col_or_gen(wpos)?; + + let mut rng = ChaChaRng::from_seed(seed_expan::rng_state(seed)); + + const BASE_ROCK_DENSITY: f64 = 0.15; + if rng.gen_bool((BASE_ROCK_DENSITY * col.rock_density as f64).clamped(0.0, 1.0)) + && col.path.map_or(true, |(d, _, _, _)| d > 6.0) + { + match ( + (col.alt - CONFIG.sea_level) as i32, + (col.alt - col.water_level) as i32, + col.water_dist.map_or(i32::MAX, |d| d as i32), + ) { + (-3..=2, _, _) => { + if rng.gen_bool(0.3) { + Some(RockKind::Rauk(Pillar::generate(&mut rng))) + } else { + Some(RockKind::Rock(VoronoiCell::generate( + rng.gen_range(1.0..3.0), + &mut rng, + ))) + } + }, + (_, -15..=3, _) => Some(RockKind::Rock(VoronoiCell::generate( + rng.gen_range(1.0..4.0), + &mut rng, + ))), + (5..=i32::MAX, _, 10..=i32::MAX) => { + if col.temp > CONFIG.desert_temp - 0.1 + && col.humidity < CONFIG.desert_hum + 0.1 + { + Some(RockKind::Sandstone(VoronoiCell::generate( + rng.gen_range(2.0..20.0 - 15.0 * col.tree_density), + &mut rng, + ))) + } else { + Some(RockKind::Rock(VoronoiCell::generate( + rng.gen_range(2.0..20.0 - 15.0 * col.tree_density), + &mut rng, + ))) + } + }, + _ => None, + } + .map(|kind| Rock { + wpos: wpos.with_z(col.alt as i32), + seed, + units: UnitChooser::new(seed).get(seed).into(), + kind, + }) + } else { + None + } + }); + + for rock in rocks { + let bounds = rock.kind.get_bounds(); + + let rpos2d = (wpos2d - rock.wpos.xy()) + .map2(rock.units, |p, unit| unit * p) + .sum(); + + if !Aabr::from(bounds).contains_point(rpos2d) { + // Skip this column + continue; + } + + let mut is_top = true; + let mut last_block = Block::empty(); + for z in (bounds.min.z..bounds.max.z).rev() { + let wpos = Vec3::new(wpos2d.x, wpos2d.y, rock.wpos.z + z); + let model_pos = (wpos - rock.wpos) + .xy() + .map2(rock.units, |rpos, unit| unit * rpos) + .sum() + .with_z(wpos.z - rock.wpos.z); + + rock.kind + .take_sample(model_pos, rock.seed, last_block, col) + .map(|block| { + if col.snow_cover && is_top && block.is_filled() { + canvas.set( + wpos + Vec3::unit_z(), + Block::new(BlockKind::Snow, Rgb::new(210, 210, 255)), + ); + } + canvas.set(wpos, block); + is_top = false; + last_block = block; + }); + } + } + }); +} + +struct VoronoiCell { + size: f32, + points: [Vec3; 26], +} + +impl VoronoiCell { + fn generate(size: f32, rng: &mut impl Rng) -> Self { + let mut points = [Vec3::zero(); 26]; + for (i, p) in NEIGHBORS3.iter().enumerate() { + points[i] = p.as_() * size + + Vec3::new( + rng.gen_range(-0.5..=0.5) * size, + rng.gen_range(-0.5..=0.5) * size, + rng.gen_range(-0.5..=0.5) * size, + ); + } + Self { size, points } + } + + fn sample_at(&self, rpos: Vec3) -> bool { + let rposf = rpos.as_(); + // Would theoretically only need to compare with 7 other points rather than 26, + // by checking all the points in the cells touching the closest corner of this + // point. + rposf.magnitude_squared() + <= *(0..26) + .map(|i| self.points[i].distance_squared(rposf)) + .map(|d| NotNan::new(d).unwrap()) + .min() + .unwrap() + } +} + +struct Pillar { + height: f32, + max_extent: Vec2, + extents: [Vec2; 3], +} + +impl Pillar { + fn generate(rng: &mut impl Rng) -> Self { + let extents = [ + Vec2::new(rng.gen_range(0.5..1.5), rng.gen_range(0.5..1.5)), + Vec2::new(rng.gen_range(0.8..2.8), rng.gen_range(0.8..2.8)), + Vec2::new(rng.gen_range(0.5..1.5), rng.gen_range(0.5..3.5)), + ]; + Self { + height: rng.gen_range(6.0..16.0), + extents, + max_extent: extents + .iter() + .cloned() + .reduce(|accum, item| accum.map2(item, |a, b| a.max(b))) + .unwrap(), + } + } + + fn sample_at(&self, rpos: Vec3) -> bool { + let h = rpos.z as f32 / self.height; + let extent = if h < 0.0 { + self.extents[0] * (-h).max(1.0) + } else if h < 0.5 { + self.extents[0].map2(self.extents[1], |l, m| f32::lerp(l, m, h * 2.0)) + } else if h < 1.0 { + self.extents[1].map2(self.extents[2], |m, t| f32::lerp(m, t, (h - 0.5) * 2.0)) + } else { + self.extents[2] + }; + h < 1.0 + && extent + .map2(rpos.xy(), |e, p| p.abs() < e.ceil() as i32) + .reduce_and() + } +} + +enum RockKind { + // A normal rock with a size + Rock(VoronoiCell), + Sandstone(VoronoiCell), + Rauk(Pillar), + // Arch, + // Hoodoos, +} + +impl RockKind { + fn take_sample( + &self, + rpos: Vec3, + seed: u32, + last_block: Block, + col: &ColumnSample, + ) -> Option { + // Used to debug get_bounds + /* + let bounds = self.get_bounds(); + if rpos + .map3( + bounds.min, + bounds.max, + |e, a, b| if e == a || e == b { 1 } else { 0 }, + ) + .sum() + >= 2 + { + return Some(Block::new(BlockKind::Rock, Rgb::red())); + } + */ + + match self { + RockKind::Rock(cell) => { + if cell.sample_at(rpos) { + let mossiness = 0.1 + + RandomField::new(seed).get_f32(Vec3::zero()) * 0.3 + + col.humidity * 0.9; + Some( + if last_block.is_filled() + || (rpos.z as f32 / cell.size + + RandomField::new(seed).get_f32(rpos) * 0.3 + > mossiness) + { + let mut i = 0; + Block::new( + BlockKind::WeakRock, + col.stone_col.map(|c| { + i += 1; + c + RandomField::new(seed).get(rpos) as u8 % 10 + }), + ) + } else { + Block::new( + BlockKind::Grass, + col.surface_color.map(|e| (e * 255.0) as u8), + ) + }, + ) + } else { + None + } + }, + RockKind::Sandstone(cell) => { + if cell.sample_at(rpos) { + let sandiness = 0.3 + RandomField::new(seed).get_f32(Vec3::zero()) * 0.4; + Some( + if last_block.is_filled() + || (rpos.z as f32 / cell.size + + RandomField::new(seed).get_f32(rpos) * 0.3 + > sandiness) + { + let mut i = 0; + Block::new( + BlockKind::WeakRock, + Rgb::new(220, 160, 100).map(|c| { + i += 1; + c + RandomField::new(seed + i).get(Vec2::zero().with_z(rpos.z)) + as u8 + % 30 + }), + ) + } else { + Block::new( + BlockKind::Grass, + col.surface_color.map(|e| (e * 255.0) as u8), + ) + }, + ) + } else { + None + } + }, + RockKind::Rauk(pillar) => { + let max_extent = *pillar + .max_extent + .map(|e| NotNan::new(e).unwrap()) + .reduce_max(); + let is_filled = |rpos| { + pillar.sample_at(rpos) + && RandomField::new(seed).chance( + rpos, + 1.5 - rpos.z as f32 / pillar.height + - rpos.xy().as_::().magnitude() / max_extent, + ) + }; + if is_filled(rpos) || + // Prevent floating blocks + (last_block.is_filled() + && NEIGHBORS + .iter() + .map(|n| !is_filled(rpos + n.with_z(0))) + .all(|b| b)) + { + Some(Block::new( + BlockKind::WeakRock, + Rgb::new( + 190 + RandomField::new(seed + 1).get(rpos) as u8 % 10, + 190 + RandomField::new(seed + 2).get(rpos) as u8 % 10, + 190 + RandomField::new(seed + 3).get(rpos) as u8 % 10, + ), + )) + } else { + None + } + }, + } + } + + fn get_bounds(&self) -> Aabb { + match self { + RockKind::Rock(VoronoiCell { size, .. }) + | RockKind::Sandstone(VoronoiCell { size, .. }) => { + // Need to use full size because rock can bleed over into other cells + let extent = *size as i32; + Aabb { + min: Vec3::broadcast(-extent), + max: Vec3::broadcast(extent), + } + }, + RockKind::Rauk(Pillar { + max_extent: extent, + height, + .. + }) => Aabb { + min: (-extent.as_()).with_z(-2), + max: extent.as_().with_z(*height as i32), + }, + } + } +} diff --git a/world/src/layer/shrub.rs b/world/src/layer/shrub.rs index 1d17e0ee0d..2c975e002a 100644 --- a/world/src/layer/shrub.rs +++ b/world/src/layer/shrub.rs @@ -1,13 +1,12 @@ use crate::{ all::ForestKind, - util::{seed_expan, Sampler, StructureGen2d, UnitChooser}, + util::{gen_cache::StructureGenCache, seed_expan, Sampler, StructureGen2d, UnitChooser}, Canvas, }; use common::{ assets::AssetHandle, terrain::structure::{Structure, StructuresGroup}, }; -use hashbrown::HashMap; use lazy_static::lazy_static; use rand::prelude::*; use rand_chacha::ChaChaRng; @@ -29,47 +28,44 @@ struct Shrub { } pub fn apply_shrubs_to(canvas: &mut Canvas, _dynamic_rng: &mut impl Rng) { - let mut shrub_cache = HashMap::new(); - - let shrub_gen = StructureGen2d::new(canvas.index().seed, 8, 4); + let mut shrub_gen = StructureGenCache::new(StructureGen2d::new(canvas.index().seed, 8, 4)); let info = canvas.info(); + canvas.foreach_col(|_, wpos2d, _| { - for (wpos, seed) in shrub_gen.get(wpos2d) { - shrub_cache.entry(wpos).or_insert_with(|| { - let col = info.col_or_gen(wpos)?; + shrub_gen.get(wpos2d, |wpos, seed| { + let col = info.col_or_gen(wpos)?; - let mut rng = ChaChaRng::from_seed(seed_expan::rng_state(seed)); + let mut rng = ChaChaRng::from_seed(seed_expan::rng_state(seed)); - const BASE_SHRUB_DENSITY: f64 = 0.15; - if rng.gen_bool((BASE_SHRUB_DENSITY * col.tree_density as f64).clamped(0.0, 1.0)) - && col.water_dist.map_or(true, |d| d > 8.0) - && col.alt > col.water_level - && col.spawn_rate > 0.9 - && col.path.map_or(true, |(d, _, _, _)| d > 6.0) - { - let kind = *info - .chunks() - .make_forest_lottery(wpos) - .choose_seeded(seed) - .as_ref()?; - if rng.gen_bool(kind.shrub_density_factor() as f64) { - Some(Shrub { - wpos: wpos.with_z(col.alt as i32), - seed, - kind, - }) - } else { - None - } + const BASE_SHRUB_DENSITY: f64 = 0.15; + if rng.gen_bool((BASE_SHRUB_DENSITY * col.tree_density as f64).clamped(0.0, 1.0)) + && col.water_dist.map_or(true, |d| d > 8.0) + && col.alt > col.water_level + && col.spawn_rate > 0.9 + && col.path.map_or(true, |(d, _, _, _)| d > 6.0) + { + let kind = *info + .chunks() + .make_forest_lottery(wpos) + .choose_seeded(seed) + .as_ref()?; + if rng.gen_bool(kind.shrub_density_factor() as f64) { + Some(Shrub { + wpos: wpos.with_z(col.alt as i32), + seed, + kind, + }) } else { None } - }); - } + } else { + None + } + }); }); - for shrub in shrub_cache.values().filter_map(|s| s.as_ref()) { + for shrub in shrub_gen.generated() { let mut rng = ChaChaRng::from_seed(seed_expan::rng_state(shrub.seed)); let units = UnitChooser::new(shrub.seed).get(shrub.seed).into(); diff --git a/world/src/layer/tree.rs b/world/src/layer/tree.rs index eaf43cc3af..7d95a85602 100644 --- a/world/src/layer/tree.rs +++ b/world/src/layer/tree.rs @@ -2,7 +2,7 @@ use crate::{ all::*, block::block_from_structure, column::ColumnGen, - util::{RandomPerm, Sampler, UnitChooser}, + util::{gen_cache::StructureGenCache, RandomPerm, Sampler, UnitChooser}, Canvas, }; use common::{ @@ -14,7 +14,6 @@ use common::{ }, vol::ReadVol, }; -use hashbrown::HashMap; use lazy_static::lazy_static; use rand::prelude::*; use std::{f32, ops::Range}; @@ -51,174 +50,161 @@ pub fn apply_trees_to( model: TreeModel, seed: u32, units: (Vec2, Vec2), + lights: bool, } - let mut tree_cache = HashMap::new(); - let info = canvas.info(); + let mut tree_cache = StructureGenCache::new(info.chunks().gen_ctx.structure_gen.clone()); + canvas.foreach_col(|canvas, wpos2d, col| { - let trees = info.chunks().get_near_trees(wpos2d); + let trees = tree_cache.get(wpos2d, |wpos, seed| { + let scale = 1.0; + let inhabited = false; + let forest_kind = *info + .chunks() + .make_forest_lottery(wpos) + .choose_seeded(seed) + .as_ref()?; - for TreeAttr { - pos, - seed, - scale, - forest_kind, - inhabited, - } in trees - { - let tree = if let Some(tree) = tree_cache.entry(pos).or_insert_with(|| { - let col = ColumnGen::new(info.chunks()).get((pos, info.index(), calendar))?; + let col = ColumnGen::new(info.chunks()).get((wpos, info.index(), calendar))?; - // Ensure that it's valid to place a *thing* here - if col.alt < col.water_level - || col.spawn_rate < 0.9 - || col.water_dist.map(|d| d < 8.0).unwrap_or(false) - || col.path.map(|(d, _, _, _)| d < 12.0).unwrap_or(false) - { - return None; - } + // Ensure that it's valid to place a *thing* here + if col.alt < col.water_level + || col.spawn_rate < 0.9 + || col.water_dist.map(|d| d < 8.0).unwrap_or(false) + || col.path.map(|(d, _, _, _)| d < 12.0).unwrap_or(false) + { + return None; + } - // Ensure that it's valid to place a tree here - if ((seed.wrapping_mul(13)) & 0xFF) as f32 / 256.0 > col.tree_density { - return None; - } + // Ensure that it's valid to place a tree here + if ((seed.wrapping_mul(13)) & 0xFF) as f32 / 256.0 > col.tree_density { + return None; + } - Some(Tree { - pos: Vec3::new(pos.x, pos.y, col.alt as i32), - model: 'model: { - let models: AssetHandle<_> = match forest_kind { - ForestKind::Oak if QUIRKY_RAND.chance(seed + 1, 1.0 / 16.0) => { - *OAK_STUMPS - }, - ForestKind::Oak if QUIRKY_RAND.chance(seed + 2, 1.0 / 20.0) => { - break 'model TreeModel::Procedural( - ProceduralTree::generate( - TreeConfig::apple(&mut RandomPerm::new(seed), scale), - &mut RandomPerm::new(seed), - ), - StructureBlock::TemperateLeaves, - ); - }, - ForestKind::Palm => *PALMS, - ForestKind::Acacia => { - break 'model TreeModel::Procedural( - ProceduralTree::generate( - TreeConfig::acacia(&mut RandomPerm::new(seed), scale), - &mut RandomPerm::new(seed), - ), - StructureBlock::Acacia, - ); - }, - ForestKind::Baobab => { - break 'model TreeModel::Procedural( - ProceduralTree::generate( - TreeConfig::baobab(&mut RandomPerm::new(seed), scale), - &mut RandomPerm::new(seed), - ), - StructureBlock::Baobab, - ); - }, - ForestKind::Oak => { - break 'model TreeModel::Procedural( - ProceduralTree::generate( - TreeConfig::oak(&mut RandomPerm::new(seed), scale), - &mut RandomPerm::new(seed), - ), - StructureBlock::TemperateLeaves, - ); - }, - ForestKind::Chestnut => { - break 'model TreeModel::Procedural( - ProceduralTree::generate( - TreeConfig::chestnut(&mut RandomPerm::new(seed), scale), - &mut RandomPerm::new(seed), - ), - StructureBlock::Chestnut, - ); - }, - ForestKind::Pine => { - break 'model TreeModel::Procedural( - ProceduralTree::generate( - TreeConfig::pine( - &mut RandomPerm::new(seed), - scale, - calendar, - ), - &mut RandomPerm::new(seed), - ), - StructureBlock::PineLeaves, - ); - }, - ForestKind::Cedar => { - break 'model TreeModel::Procedural( - ProceduralTree::generate( - TreeConfig::cedar(&mut RandomPerm::new(seed), scale), - &mut RandomPerm::new(seed), - ), - StructureBlock::PineLeaves, - ); - }, - ForestKind::Birch => { - break 'model TreeModel::Procedural( - ProceduralTree::generate( - TreeConfig::birch(&mut RandomPerm::new(seed), scale), - &mut RandomPerm::new(seed), - ), - StructureBlock::TemperateLeaves, - ); - }, - ForestKind::Frostpine => { - break 'model TreeModel::Procedural( - ProceduralTree::generate( - TreeConfig::frostpine(&mut RandomPerm::new(seed), scale), - &mut RandomPerm::new(seed), - ), - StructureBlock::FrostpineLeaves, - ); - }, + Some(Tree { + pos: Vec3::new(wpos.x, wpos.y, col.alt as i32), + model: 'model: { + let models: AssetHandle<_> = match forest_kind { + ForestKind::Oak if QUIRKY_RAND.chance(seed + 1, 1.0 / 16.0) => *OAK_STUMPS, + ForestKind::Oak if QUIRKY_RAND.chance(seed + 2, 1.0 / 20.0) => { + break 'model TreeModel::Procedural( + ProceduralTree::generate( + TreeConfig::apple(&mut RandomPerm::new(seed), scale), + &mut RandomPerm::new(seed), + ), + StructureBlock::TemperateLeaves, + ); + }, + ForestKind::Palm => *PALMS, + ForestKind::Acacia => { + break 'model TreeModel::Procedural( + ProceduralTree::generate( + TreeConfig::acacia(&mut RandomPerm::new(seed), scale), + &mut RandomPerm::new(seed), + ), + StructureBlock::Acacia, + ); + }, + ForestKind::Baobab => { + break 'model TreeModel::Procedural( + ProceduralTree::generate( + TreeConfig::baobab(&mut RandomPerm::new(seed), scale), + &mut RandomPerm::new(seed), + ), + StructureBlock::Baobab, + ); + }, + ForestKind::Oak => { + break 'model TreeModel::Procedural( + ProceduralTree::generate( + TreeConfig::oak(&mut RandomPerm::new(seed), scale), + &mut RandomPerm::new(seed), + ), + StructureBlock::TemperateLeaves, + ); + }, + ForestKind::Chestnut => { + break 'model TreeModel::Procedural( + ProceduralTree::generate( + TreeConfig::chestnut(&mut RandomPerm::new(seed), scale), + &mut RandomPerm::new(seed), + ), + StructureBlock::Chestnut, + ); + }, + ForestKind::Pine => { + break 'model TreeModel::Procedural( + ProceduralTree::generate( + TreeConfig::pine(&mut RandomPerm::new(seed), scale, calendar), + &mut RandomPerm::new(seed), + ), + StructureBlock::PineLeaves, + ); + }, + ForestKind::Cedar => { + break 'model TreeModel::Procedural( + ProceduralTree::generate( + TreeConfig::cedar(&mut RandomPerm::new(seed), scale), + &mut RandomPerm::new(seed), + ), + StructureBlock::PineLeaves, + ); + }, + ForestKind::Birch => { + break 'model TreeModel::Procedural( + ProceduralTree::generate( + TreeConfig::birch(&mut RandomPerm::new(seed), scale), + &mut RandomPerm::new(seed), + ), + StructureBlock::TemperateLeaves, + ); + }, + ForestKind::Frostpine => { + break 'model TreeModel::Procedural( + ProceduralTree::generate( + TreeConfig::frostpine(&mut RandomPerm::new(seed), scale), + &mut RandomPerm::new(seed), + ), + StructureBlock::FrostpineLeaves, + ); + }, - ForestKind::Mangrove => { - break 'model TreeModel::Procedural( - ProceduralTree::generate( - TreeConfig::jungle(&mut RandomPerm::new(seed), scale), - &mut RandomPerm::new(seed), - ), - StructureBlock::Mangrove, - ); - }, - ForestKind::Swamp => *SWAMP_TREES, - ForestKind::Giant => { - break 'model TreeModel::Procedural( - ProceduralTree::generate( - TreeConfig::giant( - &mut RandomPerm::new(seed), - scale, - inhabited, - ), - &mut RandomPerm::new(seed), - ), - StructureBlock::TemperateLeaves, - ); - }, - }; + ForestKind::Mangrove => { + break 'model TreeModel::Procedural( + ProceduralTree::generate( + TreeConfig::jungle(&mut RandomPerm::new(seed), scale), + &mut RandomPerm::new(seed), + ), + StructureBlock::Mangrove, + ); + }, + ForestKind::Swamp => *SWAMP_TREES, + ForestKind::Giant => { + break 'model TreeModel::Procedural( + ProceduralTree::generate( + TreeConfig::giant(&mut RandomPerm::new(seed), scale, inhabited), + &mut RandomPerm::new(seed), + ), + StructureBlock::TemperateLeaves, + ); + }, + }; - let models = models.read(); - TreeModel::Structure( - models[(MODEL_RAND.get(seed.wrapping_mul(17)) / 13) as usize - % models.len()] - .clone(), - ) - }, - seed, - units: UNIT_CHOOSER.get(seed), - }) - }) { - tree - } else { - continue; - }; + let models = models.read(); + TreeModel::Structure( + models + [(MODEL_RAND.get(seed.wrapping_mul(17)) / 13) as usize % models.len()] + .clone(), + ) + }, + seed, + units: UNIT_CHOOSER.get(seed), + lights: inhabited, + }) + }); + for tree in trees { let bounds = match &tree.model { TreeModel::Structure(s) => s.get_bounds(), TreeModel::Procedural(t, _) => t.get_bounds().map(|e| e as i32), @@ -279,7 +265,7 @@ pub fn apply_trees_to( ) .map(|block| { // Add lights to the tree - if inhabited + if tree.lights && last_block.is_air() && block.kind() == BlockKind::Wood && dynamic_rng.gen_range(0..256) == 0 diff --git a/world/src/layer/wildlife.rs b/world/src/layer/wildlife.rs index 81199079c6..58307485ff 100644 --- a/world/src/layer/wildlife.rs +++ b/world/src/layer/wildlife.rs @@ -137,7 +137,7 @@ pub fn spawn_manifest() -> Vec<(&'static str, DensityFn)> { // **Tundra** // Rock animals ("world.wildlife.spawn.tundra.rock", |c, col| { - close(c.temp, CONFIG.snow_temp, 0.15) * BASE_DENSITY * col.rock * 1.0 + close(c.temp, CONFIG.snow_temp, 0.15) * BASE_DENSITY * col.rock_density * 1.0 }), // Core animals ("world.wildlife.spawn.tundra.core", |c, _col| { @@ -282,7 +282,7 @@ pub fn spawn_manifest() -> Vec<(&'static str, DensityFn)> { }), // Rock animals ("world.wildlife.spawn.tropical.rock", |c, col| { - close(c.temp, CONFIG.tropical_temp + 0.1, 0.5) * col.rock * BASE_DENSITY * 5.0 + close(c.temp, CONFIG.tropical_temp + 0.1, 0.5) * col.rock_density * BASE_DENSITY * 5.0 }), // **Desert** // Area animals diff --git a/world/src/lib.rs b/world/src/lib.rs index 6079a8b3f8..4bd69d869e 100644 --- a/world/src/lib.rs +++ b/world/src/lib.rs @@ -366,6 +366,9 @@ impl World { if index.features.caves { layer::apply_caves_to(&mut canvas, &mut dynamic_rng); } + if index.features.rocks { + layer::apply_rocks_to(&mut canvas, &mut dynamic_rng); + } if index.features.shrubs { layer::apply_shrubs_to(&mut canvas, &mut dynamic_rng); } diff --git a/world/src/sim/mod.rs b/world/src/sim/mod.rs index 2d1f8d21b1..e050f0d048 100644 --- a/world/src/sim/mod.rs +++ b/world/src/sim/mod.rs @@ -2124,6 +2124,7 @@ impl WorldSim { ) } + /// WARNING: Not currently used by the tree layer. Needs to be reworked. /// Return an iterator over candidate tree positions (note that only some of /// these will become trees since environmental parameters may forbid /// them spawning). diff --git a/world/src/util/gen_cache.rs b/world/src/util/gen_cache.rs new file mode 100644 index 0000000000..7584f53b23 --- /dev/null +++ b/world/src/util/gen_cache.rs @@ -0,0 +1,41 @@ +use hashbrown::HashMap; +use vek::Vec2; + +use super::{Sampler, StructureGen2d}; + +pub struct StructureGenCache { + gen: StructureGen2d, + // TODO: Compare performance of using binary search instead of hashmap + cache: HashMap, Option>, +} + +impl StructureGenCache { + pub fn new(gen: StructureGen2d) -> Self { + Self { + gen, + cache: HashMap::new(), + } + } + + pub fn get( + &mut self, + index: Vec2, + mut generate: impl FnMut(Vec2, u32) -> Option, + ) -> Vec<&T> { + let close = self.gen.get(index); + for (wpos, seed) in close { + self.cache + .entry(wpos) + .or_insert_with(|| generate(wpos, seed)); + } + + close + .iter() + .filter_map(|(wpos, _)| self.cache.get(wpos).unwrap().as_ref()) + .collect() + } + + pub fn generated(&self) -> impl Iterator { + self.cache.values().filter_map(|v| v.as_ref()) + } +} diff --git a/world/src/util/mod.rs b/world/src/util/mod.rs index 27cc30c080..7c3c741bcc 100644 --- a/world/src/util/mod.rs +++ b/world/src/util/mod.rs @@ -1,4 +1,5 @@ pub mod fast_noise; +pub mod gen_cache; pub mod map_array; pub mod map_vec; pub mod math; @@ -64,6 +65,35 @@ pub const NEIGHBORS: [Vec2; 8] = [ Vec2::new(1, -1), ]; +pub const NEIGHBORS3: [Vec3; 26] = [ + Vec3::new(0, 0, -1), + Vec3::new(0, 0, 1), + Vec3::new(0, -1, 0), + Vec3::new(0, -1, -1), + Vec3::new(0, -1, 1), + Vec3::new(0, 1, 0), + Vec3::new(0, 1, -1), + Vec3::new(0, 1, 1), + Vec3::new(-1, 0, 0), + Vec3::new(-1, 0, -1), + Vec3::new(-1, 0, 1), + Vec3::new(-1, -1, 0), + Vec3::new(-1, -1, -1), + Vec3::new(-1, -1, 1), + Vec3::new(-1, 1, 0), + Vec3::new(-1, 1, -1), + Vec3::new(-1, 1, 1), + Vec3::new(1, 0, 0), + Vec3::new(1, 0, -1), + Vec3::new(1, 0, 1), + Vec3::new(1, -1, 0), + Vec3::new(1, -1, -1), + Vec3::new(1, -1, 1), + Vec3::new(1, 1, 0), + Vec3::new(1, 1, -1), + Vec3::new(1, 1, 1), +]; + pub const LOCALITY: [Vec2; 9] = [ Vec2::new(0, 0), Vec2::new(0, 1), diff --git a/world/src/util/random.rs b/world/src/util/random.rs index 2e85f2f978..54aacf42ee 100644 --- a/world/src/util/random.rs +++ b/world/src/util/random.rs @@ -10,8 +10,10 @@ pub struct RandomField { impl RandomField { pub const fn new(seed: u32) -> Self { Self { seed } } - pub fn chance(&self, pos: Vec3, chance: f32) -> bool { - (self.get(pos) % (1 << 16)) as f32 / ((1 << 16) as f32) < chance + pub fn chance(&self, pos: Vec3, chance: f32) -> bool { self.get_f32(pos) < chance } + + pub fn get_f32(&self, pos: Vec3) -> f32 { + (self.get(pos) % (1 << 16)) as f32 / ((1 << 16) as f32) } } diff --git a/world/src/util/structure.rs b/world/src/util/structure.rs index 48429f3e65..2b7b8bfa1a 100644 --- a/world/src/util/structure.rs +++ b/world/src/util/structure.rs @@ -2,6 +2,7 @@ use super::{RandomField, Sampler}; use rayon::prelude::*; use vek::*; +#[derive(Clone)] pub struct StructureGen2d { freq: u32, spread: u32,