diff --git a/assets/voxygen/shaders/include/cloud/regular.glsl b/assets/voxygen/shaders/include/cloud/regular.glsl index 1b3b592c75..a53fb17795 100644 --- a/assets/voxygen/shaders/include/cloud/regular.glsl +++ b/assets/voxygen/shaders/include/cloud/regular.glsl @@ -132,7 +132,7 @@ vec3 get_cloud_color(vec3 surf_color, vec3 dir, vec3 origin, const float time_of float ndist = step_to_dist(trunc(dist_to_step(cdist - 0.25))); vec3 sample = cloud_at(origin + (dir + dir_diff / ndist) * ndist * splay, ndist); - vec2 density_integrals = sample.yz * (cdist - ndist); + vec2 density_integrals = max(sample.yz, vec2(0)) * (cdist - ndist); float sun_access = sample.x; float scatter_factor = 1.0 - 1.0 / (1.0 + density_integrals.x); diff --git a/server/src/lib.rs b/server/src/lib.rs index c1f98707b8..79980fbc18 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -281,7 +281,7 @@ impl Server { .expect(&format!("no z_cache found for chunk: {}", spawn_chunk)); // get the minimum and maximum z values at which there could be solid blocks - let (min_z, _, max_z) = z_cache.get_z_limits(&mut block_sampler, index); + let (min_z, max_z) = z_cache.get_z_limits(); // round range outwards, so no potential air block is missed let min_z = min_z.floor() as i32; let max_z = max_z.ceil() as i32; @@ -296,8 +296,6 @@ impl Server { .get_with_z_cache( Vec3::new(spawn_location.x, spawn_location.y, *z), Some(&z_cache), - false, - index, ) .map(|b| b.is_air()) .unwrap_or(false) diff --git a/world/src/block/mod.rs b/world/src/block/mod.rs index 0896dd7f79..d19ab5569f 100644 --- a/world/src/block/mod.rs +++ b/world/src/block/mod.rs @@ -1,16 +1,11 @@ -mod natural; - use crate::{ column::{ColumnGen, ColumnSample}, util::{RandomField, Sampler, SmallCache}, IndexRef, }; -use common::{ - terrain::{ - structure::{self, StructureBlock}, - Block, BlockKind, SpriteKind, Structure, - }, - vol::ReadVol, +use common::terrain::{ + structure::{self, StructureBlock}, + Block, BlockKind, SpriteKind, }; use core::ops::{Div, Mul, Range}; use serde::Deserialize; @@ -18,7 +13,6 @@ use vek::*; #[derive(Deserialize)] pub struct Colors { - pub pyramid: (u8, u8, u8), // TODO(@Sharp): After the merge, construct enough infrastructure to make it convenient to // define mapping functions over the input; i.e. we should be able to interpret some fields as // defining App, Arg>, where Fun : (Context, Arg) → (S, Type). @@ -26,17 +20,11 @@ pub struct Colors { } pub struct BlockGen<'a> { - pub column_cache: SmallCache>>, pub column_gen: ColumnGen<'a>, } impl<'a> BlockGen<'a> { - pub fn new(column_gen: ColumnGen<'a>) -> Self { - Self { - column_cache: SmallCache::default(), - column_gen, - } - } + pub fn new(column_gen: ColumnGen<'a>) -> Self { Self { column_gen } } pub fn sample_column<'b>( column_gen: &ColumnGen<'a>, @@ -49,119 +37,17 @@ impl<'a> BlockGen<'a> { .as_ref() } - pub fn get_cliff_height( - column_gen: &ColumnGen<'a>, - cache: &mut SmallCache>>, - wpos: Vec2, - close_cliffs: &[(Vec2, u32); 9], - cliff_hill: f32, - tolerance: f32, - index: IndexRef<'a>, - ) -> f32 { - close_cliffs.iter().fold( - 0.0f32, - |max_height, (cliff_pos, seed)| match Self::sample_column( - column_gen, cache, *cliff_pos, index, - ) { - Some(cliff_sample) if cliff_sample.is_cliffs && cliff_sample.spawn_rate > 0.5 => { - let cliff_pos3d = Vec3::from(*cliff_pos); - - // Conservative range of height: [15.70, 49.33] - let height = (RandomField::new(seed + 1).get(cliff_pos3d) % 64) as f32 - // [0, 63] / (1 + 3 * [0.12, 1.32]) + 3 = - // [0, 63] / (1 + [0.36, 3.96]) + 3 = - // [0, 63] / [1.36, 4.96] + 3 = - // [0, 63] / [1.36, 4.96] + 3 = - // (height min) [0, 0] + 3 = [3, 3] - // (height max) [12.70, 46.33] + 3 = [15.70, 49.33] - / (1.0 + 3.0 * cliff_sample.chaos) - + 3.0; - // Conservative range of radius: [8, 47] - let radius = RandomField::new(seed + 2).get(cliff_pos3d) % 48 + 8; - - if cliff_sample - .water_dist - .map(|d| d > radius as f32) - .unwrap_or(true) - { - max_height.max( - if cliff_pos.map(|e| e as f32).distance_squared(wpos) - < (radius as f32 + tolerance).powf(2.0) - { - cliff_sample.alt + height * (1.0 - cliff_sample.chaos) + cliff_hill - } else { - 0.0 - }, - ) - } else { - max_height - } - }, - _ => max_height, - }, - ) - } - pub fn get_z_cache(&mut self, wpos: Vec2, index: IndexRef<'a>) -> Option> { - let BlockGen { - column_cache, - column_gen, - } = self; + let BlockGen { column_gen } = self; // Main sample let sample = column_gen.get((wpos, index))?; - // Tree samples - let mut structures = [None, None, None, None, None, None, None, None, None]; - sample - .close_structures - .iter() - .zip(structures.iter_mut()) - .for_each(|(close_structure, structure)| { - if let Some(st) = *close_structure { - let st_sample = Self::sample_column(column_gen, column_cache, st.pos, index); - if let Some(st_sample) = st_sample { - let st_sample = st_sample.clone(); - let st_info = match st.meta { - None => natural::structure_gen( - column_gen, - column_cache, - st.pos, - st.seed, - &st_sample, - index, - ), - Some(meta) => Some(StructureInfo { - pos: Vec3::from(st.pos) + Vec3::unit_z() * st_sample.alt as i32, - seed: st.seed, - meta, - }), - }; - if let Some(st_info) = st_info { - *structure = Some((st_info, st_sample)); - } - } - } - }); - - Some(ZCache { - wpos, - sample, - structures, - }) + Some(ZCache { sample }) } - pub fn get_with_z_cache( - &mut self, - wpos: Vec3, - z_cache: Option<&ZCache>, - only_structures: bool, - index: IndexRef<'a>, - ) -> Option { - let BlockGen { - column_cache, - column_gen, - } = self; + pub fn get_with_z_cache(&mut self, wpos: Vec3, z_cache: Option<&ZCache>) -> Option { + let BlockGen { column_gen } = self; let world = column_gen.sim; let z_cache = z_cache?; @@ -180,300 +66,133 @@ impl<'a> BlockGen<'a> { // marble, // marble_small, rock, - //cliffs, - cliff_hill, - close_cliffs, // temp, // humidity, stone_col, .. } = sample; - let structures = &z_cache.structures; - let wposf = wpos.map(|e| e as f64); - let (block, _height) = if !only_structures { - let (_definitely_underground, height, _on_cliff, basement_height, water_height) = - if (wposf.z as f32) < alt - 64.0 * chaos { - // Shortcut warping - (true, alt, false, basement, water_level) - } else { - // Apply warping - let warp = world - .gen_ctx - .warp_nz - .get(wposf.div(24.0)) - .mul((chaos - 0.1).max(0.0).min(1.0).powf(2.0)) - .mul(16.0); - let warp = Lerp::lerp(0.0, warp, warp_factor); + let (_definitely_underground, height, basement_height, water_height) = + if (wposf.z as f32) < alt - 64.0 * chaos { + // Shortcut warping + (true, alt, basement, water_level) + } else { + // Apply warping + let warp = world + .gen_ctx + .warp_nz + .get(wposf.div(24.0)) + .mul((chaos - 0.1).max(0.0).min(1.0).powf(2.0)) + .mul(16.0); + let warp = Lerp::lerp(0.0, warp, warp_factor); - let surface_height = alt + warp; + let height = alt + warp; - let (height, on_cliff) = if (wposf.z as f32) < alt + warp - 10.0 { - // Shortcut cliffs - (surface_height, false) + ( + false, + height, + basement + height - alt, + (if water_level <= alt { + water_level + warp } else { - let turb = Vec2::new( - world.gen_ctx.fast_turb_x_nz.get(wposf.div(25.0)) as f32, - world.gen_ctx.fast_turb_y_nz.get(wposf.div(25.0)) as f32, - ) * 8.0; - - let wpos_turb = Vec2::from(wpos).map(|e: i32| e as f32) + turb; - let cliff_height = Self::get_cliff_height( - column_gen, - column_cache, - wpos_turb, - &close_cliffs, - cliff_hill, - 0.0, - index, - ); - - ( - surface_height.max(cliff_height), - cliff_height > surface_height + 16.0, - ) - }; - - ( - false, - height, - on_cliff, - basement + height - alt, - (if water_level <= alt { - water_level + warp - } else { - water_level - }), - ) - }; - - // Sample blocks - - let water = Block::new(BlockKind::Water, Rgb::zero()); - - let grass_depth = (1.5 + 2.0 * chaos).min(height - basement_height); - let block = if (wposf.z as f32) < height - grass_depth { - let stone_factor = (height - grass_depth - wposf.z as f32) * 0.15; - let col = Lerp::lerp( - sub_surface_color, - stone_col.map(|e| e as f32 / 255.0), - stone_factor, + water_level + }), ) - .map(|e| (e * 255.0) as u8); + }; - if stone_factor >= 0.5 { - Some(Block::new(BlockKind::Rock, col)) + // Sample blocks + + let water = Block::new(BlockKind::Water, Rgb::zero()); + + let grass_depth = (1.5 + 2.0 * chaos).min(height - basement_height); + let block = if (wposf.z as f32) < height - grass_depth { + let stone_factor = (height - grass_depth - wposf.z as f32) * 0.15; + let col = Lerp::lerp( + sub_surface_color, + stone_col.map(|e| e as f32 / 255.0), + stone_factor, + ) + .map(|e| (e * 255.0) as u8); + + if stone_factor >= 0.5 { + Some(Block::new(BlockKind::Rock, col)) + } else { + Some(Block::new(BlockKind::Earth, col)) + } + } else if (wposf.z as f32) < height { + let grass_factor = (wposf.z as f32 - (height - grass_depth)) + .div(grass_depth) + .powf(0.5); + let col = Lerp::lerp(sub_surface_color, surface_color, grass_factor); + // Surface + Some(Block::new( + if grass_factor > 0.7 { + BlockKind::Grass } else { - Some(Block::new(BlockKind::Earth, col)) - } - } else if (wposf.z as f32) < height { - let grass_factor = (wposf.z as f32 - (height - grass_depth)) - .div(grass_depth) - .powf(0.5); - let col = Lerp::lerp(sub_surface_color, surface_color, grass_factor); - // Surface + BlockKind::Earth + }, + col.map(|e| (e * 255.0) as u8), + )) + } else { + None + } + .or_else(|| { + // Rocks + if (height + 2.5 - wposf.z as f32).div(7.5).abs().powf(2.0) < rock { + #[allow(clippy::identity_op)] + let field0 = RandomField::new(world.seed + 0); + let field1 = RandomField::new(world.seed + 1); + let field2 = RandomField::new(world.seed + 2); + Some(Block::new( - if grass_factor > 0.7 { - BlockKind::Grass - } else { - BlockKind::Earth - }, - col.map(|e| (e * 255.0) as u8), + 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(|| { - // Rocks - if (height + 2.5 - wposf.z as f32).div(7.5).abs().powf(2.0) < rock { - #[allow(clippy::identity_op)] - 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(|| { - // Water - if (wposf.z as f32) < water_height { - // Ocean - Some(water) - } else { - None - } - }); - - (block, height) - } else { - (None, sample.alt) - }; - - let block = structures - .iter() - .find_map(|st| { - let (st, st_sample) = st.as_ref()?; - st.get(index, wpos, st_sample) - }) - .or(block); + }) + .or_else(|| { + // Water + if (wposf.z as f32) < water_height { + // Ocean + Some(water) + } else { + None + } + }); block } } pub struct ZCache<'a> { - wpos: Vec2, pub sample: ColumnSample<'a>, - structures: [Option<(StructureInfo, ColumnSample<'a>)>; 9], } impl<'a> ZCache<'a> { - pub fn get_z_limits<'b>( - &self, - block_gen: &mut BlockGen<'b>, - index: IndexRef<'b>, - ) -> (f32, f32, f32) { + pub fn get_z_limits(&self) -> (f32, f32) { let min = self.sample.alt - (self.sample.chaos.min(1.0) * 16.0); let min = min - 4.0; - let cliff = BlockGen::get_cliff_height( - &block_gen.column_gen, - &mut block_gen.column_cache, - self.wpos.map(|e| e as f32), - &self.sample.close_cliffs, - self.sample.cliff_hill, - 32.0, - index, - ); - let rocks = if self.sample.rock > 0.0 { 12.0 } else { 0.0 }; let warp = self.sample.chaos * 32.0; - let (structure_min, structure_max) = self - .structures - .iter() - .filter_map(|st| st.as_ref()) - .fold((0.0f32, 0.0f32), |(min, max), (st_info, _st_sample)| { - let bounds = st_info.get_bounds(); - let st_area = Aabr { - min: Vec2::from(bounds.min), - max: Vec2::from(bounds.max), - }; + let ground_max = self.sample.alt + warp + rocks + 2.0; - if st_area.contains_point(self.wpos - st_info.pos) { - (min.min(bounds.min.z as f32), max.max(bounds.max.z as f32)) - } else { - (min, max) - } - }); + let max = ground_max.max(self.sample.water_level + 2.0); - let ground_max = (self.sample.alt + warp + rocks).max(cliff) + 2.0; - - let min = min + structure_min; - let max = (ground_max + structure_max).max(self.sample.water_level + 2.0); - - let structures_only_min_z = ground_max.max(self.sample.water_level + 2.0); - - (min, structures_only_min_z, max) - } -} - -#[derive(Copy, Clone)] -pub enum StructureMeta { - Pyramid { - height: i32, - }, - Volume { - units: (Vec2, Vec2), - volume: &'static Structure, - }, -} - -pub struct StructureInfo { - pos: Vec3, - seed: u32, - meta: StructureMeta, -} - -impl StructureInfo { - fn get_bounds(&self) -> Aabb { - match self.meta { - StructureMeta::Pyramid { height } => { - let base = 40; - Aabb { - min: Vec3::new(-base - height, -base - height, -base), - max: Vec3::new(base + height, base + height, height), - } - }, - StructureMeta::Volume { units, volume } => { - let bounds = volume.get_bounds(); - - (Aabb { - min: Vec3::from(units.0 * bounds.min.x + units.1 * bounds.min.y) - + Vec3::unit_z() * bounds.min.z, - max: Vec3::from(units.0 * bounds.max.x + units.1 * bounds.max.y) - + Vec3::unit_z() * bounds.max.z, - }) - .made_valid() - }, - } - } - - fn get(&self, index: IndexRef, wpos: Vec3, sample: &ColumnSample) -> Option { - match self.meta { - StructureMeta::Pyramid { height } => { - if wpos.z - self.pos.z - < height - - Vec2::from(wpos - self.pos) - .map(|e: i32| (e.abs() / 2) * 2) - .reduce_max() - { - Some(Block::new( - BlockKind::Rock, - index.colors.block.pyramid.into(), - )) - } else { - None - } - }, - StructureMeta::Volume { units, volume } => { - let rpos = wpos - self.pos; - let block_pos = Vec3::unit_z() * rpos.z - + Vec3::from(units.0) * rpos.x - + Vec3::from(units.1) * rpos.y; - - volume - .get((block_pos * 128) / 128) // Scaling - .ok() - .and_then(|b| { - block_from_structure( - index, - *b, - block_pos, - self.pos.into(), - self.seed, - sample, - // TODO: Take environment into account. - Block::air, - ) - }) - }, - } + (min, max) } } diff --git a/world/src/block/natural.rs b/world/src/block/natural.rs deleted file mode 100644 index 8ccff506b0..0000000000 --- a/world/src/block/natural.rs +++ /dev/null @@ -1,91 +0,0 @@ -use super::{BlockGen, StructureInfo, StructureMeta}; -use crate::{ - all::ForestKind, - column::{ColumnGen, ColumnSample}, - util::{RandomPerm, Sampler, SmallCache, UnitChooser}, - IndexRef, CONFIG, -}; -use common::terrain::Structure; -use lazy_static::lazy_static; -use std::{sync::Arc, u32}; -use vek::*; - -static VOLUME_RAND: RandomPerm = RandomPerm::new(0xDB21C052); -static UNIT_CHOOSER: UnitChooser = UnitChooser::new(0x700F4EC7); -static QUIRKY_RAND: RandomPerm = RandomPerm::new(0xA634460F); - -pub fn structure_gen<'a>( - column_gen: &ColumnGen<'a>, - column_cache: &mut SmallCache>>, - st_pos: Vec2, - st_seed: u32, - st_sample: &ColumnSample, - index: IndexRef<'a>, -) -> Option { - // Assuming it's a tree... figure out when it SHOULDN'T spawn - let random_seed = (st_seed as f64) / (u32::MAX as f64); - if (st_sample.tree_density as f64) < random_seed - || st_sample.alt < st_sample.water_level - || st_sample.spawn_rate < 0.5 - || st_sample.water_dist.map(|d| d < 8.0).unwrap_or(false) - || st_sample.path.map(|(d, _, _, _)| d < 12.0).unwrap_or(false) - { - return None; - } - - let cliff_height = BlockGen::get_cliff_height( - column_gen, - column_cache, - st_pos.map(|e| e as f32), - &st_sample.close_cliffs, - st_sample.cliff_hill, - 0.0, - index, - ); - - let wheight = st_sample.alt.max(cliff_height); - let st_pos3d = Vec3::new(st_pos.x, st_pos.y, wheight as i32); - - let volumes: &'static [_] = if QUIRKY_RAND.get(st_seed) % 512 == 17 { - if st_sample.temp > CONFIG.desert_temp { - &QUIRKY_DRY - } else { - &QUIRKY - } - } else { - match st_sample.forest_kind { - ForestKind::Palm => &PALMS, - ForestKind::Savannah => &ACACIAS, - ForestKind::Oak if QUIRKY_RAND.get(st_seed) % 16 == 7 => &OAK_STUMPS, - ForestKind::Oak if QUIRKY_RAND.get(st_seed) % 19 == 7 => &FRUIT_TREES, - ForestKind::Oak if QUIRKY_RAND.get(st_seed) % 14 == 7 => &BIRCHES, - ForestKind::Oak => &OAKS, - ForestKind::Pine => &PINES, - ForestKind::SnowPine => &SNOW_PINES, - ForestKind::Mangrove => &MANGROVE_TREES, - } - }; - - Some(StructureInfo { - pos: st_pos3d, - seed: st_seed, - meta: StructureMeta::Volume { - units: UNIT_CHOOSER.get(st_seed), - volume: &volumes[(VOLUME_RAND.get(st_seed) / 13) as usize % volumes.len()], - }, - }) -} - -lazy_static! { - pub static ref OAKS: Vec> = Structure::load_group("oaks"); - pub static ref OAK_STUMPS: Vec> = Structure::load_group("oak_stumps"); - pub static ref PINES: Vec> = Structure::load_group("pines"); - pub static ref PALMS: Vec> = Structure::load_group("palms"); - pub static ref SNOW_PINES: Vec> = Structure::load_group("snow_pines"); - pub static ref ACACIAS: Vec> = Structure::load_group("acacias"); - pub static ref FRUIT_TREES: Vec> = Structure::load_group("fruit_trees"); - pub static ref BIRCHES: Vec> = Structure::load_group("birch"); - pub static ref MANGROVE_TREES: Vec> = Structure::load_group("mangrove_trees"); - pub static ref QUIRKY: Vec> = Structure::load_group("quirky"); - pub static ref QUIRKY_DRY: Vec> = Structure::load_group("quirky_dry"); -} diff --git a/world/src/canvas.rs b/world/src/canvas.rs index e1c1b07862..d6ed1acd14 100644 --- a/world/src/canvas.rs +++ b/world/src/canvas.rs @@ -1,32 +1,39 @@ -use vek::*; -use common::{ - terrain::{TerrainChunk, Block, TerrainChunkSize}, - vol::{ReadVol, WriteVol, RectVolSize}, -}; use crate::{ block::ZCache, - util::Grid, column::ColumnSample, index::IndexRef, + sim::{SimChunk, WorldSim as Land}, + util::Grid, }; +use common::{ + terrain::{Block, TerrainChunk, TerrainChunkSize}, + vol::{ReadVol, RectVolSize, WriteVol}, +}; +use std::ops::Deref; +use vek::*; +#[derive(Copy, Clone)] pub struct CanvasInfo<'a> { pub(crate) wpos: Vec2, pub(crate) column_grid: &'a Grid>>, pub(crate) column_grid_border: i32, + pub(crate) land: &'a Land, pub(crate) index: IndexRef<'a>, + pub(crate) chunk: &'a SimChunk, } impl<'a> CanvasInfo<'a> { - pub fn wpos(&self) -> Vec2 { - self.wpos - } + pub fn wpos(&self) -> Vec2 { self.wpos } pub fn area(&self) -> Aabr { - Rect::from((self.wpos(), Extent2::from(TerrainChunkSize::RECT_SIZE.map(|e| e as i32)))).into() + Rect::from(( + self.wpos(), + Extent2::from(TerrainChunkSize::RECT_SIZE.map(|e| e as i32)), + )) + .into() } - pub fn col(&self, pos: Vec2) -> Option<&ColumnSample> { + pub fn col(&self, pos: Vec2) -> Option<&'a ColumnSample> { self.column_grid .get(self.column_grid_border + pos - self.wpos()) .map(Option::as_ref) @@ -34,24 +41,24 @@ impl<'a> CanvasInfo<'a> { .map(|zc| &zc.sample) } - pub fn index(&self) -> IndexRef { - self.index - } + pub fn index(&self) -> IndexRef<'a> { self.index } + + pub fn chunk(&self) -> &'a SimChunk { self.chunk } + + pub fn land(&self) -> &'a Land { self.land } } pub struct Canvas<'a> { - pub(crate) wpos: Vec2, + pub(crate) info: CanvasInfo<'a>, pub(crate) chunk: &'a mut TerrainChunk, } impl<'a> Canvas<'a> { - pub fn wpos(&self) -> Vec2 { - self.wpos - } - - pub fn area(&self) -> Aabr { - Rect::from((self.wpos(), Extent2::from(TerrainChunkSize::RECT_SIZE.map(|e| e as i32)))).into() - } + /// The borrow checker complains at immutable features of canvas (column + /// sampling, etc.) being used at the same time as mutable features + /// (writing blocks). To avoid this, this method extracts the + /// inner `CanvasInfo` such that it may be used independently. + pub fn info(&mut self) -> CanvasInfo<'a> { self.info } pub fn get(&mut self, pos: Vec3) -> Option { self.chunk.get(pos - self.wpos()).ok().copied() @@ -60,4 +67,30 @@ impl<'a> Canvas<'a> { pub fn set(&mut self, pos: Vec3, block: Block) { let _ = self.chunk.set(pos - self.wpos(), block); } + + pub fn map(&mut self, pos: Vec3, f: impl FnOnce(Block) -> Block) { + let _ = self.chunk.map(pos - self.wpos(), f); + } + + /// Execute an operation upon each column in this canvas. + pub fn foreach_col(&mut self, mut f: impl FnMut(&mut Self, Vec2, &ColumnSample)) { + for y in 0..self.area().size().h as i32 { + for x in 0..self.area().size().w as i32 { + let wpos2d = self.wpos() + Vec2::new(x, y); + let info = self.info; + let col = if let Some(col) = info.col(wpos2d) { + col + } else { + return; + }; + f(self, wpos2d, col); + } + } + } +} + +impl<'a> Deref for Canvas<'a> { + type Target = CanvasInfo<'a>; + + fn deref(&self) -> &Self::Target { &self.info } } diff --git a/world/src/column/mod.rs b/world/src/column/mod.rs index 665b98fe86..73bdd597ce 100644 --- a/world/src/column/mod.rs +++ b/world/src/column/mod.rs @@ -1,6 +1,5 @@ use crate::{ all::ForestKind, - block::StructureMeta, sim::{local_cells, Cave, Path, RiverKind, SimChunk, WorldSim}, util::Sampler, IndexRef, CONFIG, @@ -54,56 +53,6 @@ pub struct Colors { impl<'a> ColumnGen<'a> { pub fn new(sim: &'a WorldSim) -> Self { Self { sim } } - - #[allow(clippy::if_same_then_else)] // TODO: Pending review in #587 - fn get_local_structure(&self, wpos: Vec2) -> Option { - let (pos, seed) = self - .sim - .gen_ctx - .region_gen - .get(wpos) - .iter() - .copied() - .min_by_key(|(pos, _)| pos.distance_squared(wpos)) - .unwrap(); - - let chunk_pos = pos.map2(TerrainChunkSize::RECT_SIZE, |e, sz: u32| e / sz as i32); - let chunk = self.sim.get(chunk_pos)?; - - if seed % 5 == 2 - && chunk.temp > CONFIG.desert_temp - && chunk.alt > chunk.water_alt + 5.0 - && chunk.chaos <= 0.35 - { - /*Some(StructureData { - pos, - seed, - meta: Some(StructureMeta::Pyramid { height: 140 }), - })*/ - None - } else { - None - } - } - - fn gen_close_structures(&self, wpos: Vec2) -> [Option; 9] { - let mut metas = [None; 9]; - self.sim - .gen_ctx - .structure_gen - .get(wpos) - .iter() - .copied() - .enumerate() - .for_each(|(i, (pos, seed))| { - metas[i] = self.get_local_structure(pos).or(Some(StructureData { - pos, - seed, - meta: None, - })); - }); - metas - } } impl<'a> Sampler<'a> for ColumnGen<'a> { @@ -1053,7 +1002,6 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { 0.0 }, forest_kind: sim_chunk.forest_kind, - close_structures: self.gen_close_structures(wpos), marble, marble_small, rock, @@ -1086,7 +1034,6 @@ pub struct ColumnSample<'a> { pub sub_surface_color: Rgb, pub tree_density: f32, pub forest_kind: ForestKind, - pub close_structures: [Option; 9], pub marble: f32, pub marble_small: f32, pub rock: f32, @@ -1104,10 +1051,3 @@ pub struct ColumnSample<'a> { pub chunk: &'a SimChunk, } - -#[derive(Copy, Clone)] -pub struct StructureData { - pub pos: Vec2, - pub seed: u32, - pub meta: Option, -} diff --git a/world/src/layer/mod.rs b/world/src/layer/mod.rs index 61dacb4efa..652bea0df0 100644 --- a/world/src/layer/mod.rs +++ b/world/src/layer/mod.rs @@ -1,13 +1,12 @@ pub mod scatter; +pub mod tree; -pub use self::scatter::apply_scatter_to; +pub use self::{scatter::apply_scatter_to, tree::apply_trees_to}; use crate::{ column::ColumnSample, util::{RandomField, Sampler}, - IndexRef, - Canvas, - CanvasInfo, + Canvas, IndexRef, }; use common::{ assets::Asset, @@ -34,178 +33,162 @@ pub struct Colors { const EMPTY_AIR: Block = Block::air(SpriteKind::Empty); -pub fn apply_paths_to<'a>( - canvas: &mut Canvas, - info: &CanvasInfo, -) { - for y in 0..canvas.area().size().h as i32 { - for x in 0..canvas.area().size().w as i32 { - let offs = Vec2::new(x, y); +pub fn apply_paths_to<'a>(canvas: &mut Canvas) { + let info = canvas.info(); + canvas.foreach_col(|canvas, wpos2d, col| { + let surface_z = col.riverless_alt.floor() as i32; - let wpos2d = canvas.wpos() + offs; + let noisy_color = |color: Rgb, factor: u32| { + let nz = RandomField::new(0).get(Vec3::new(wpos2d.x, wpos2d.y, surface_z)); + color.map(|e| { + (e as u32 + nz % (factor * 2)) + .saturating_sub(factor) + .min(255) as u8 + }) + }; - // Sample terrain - let col_sample = if let Some(col_sample) = info.col(wpos2d) { - col_sample - } else { - continue; + if let Some((path_dist, path_nearest, path, _)) = + col.path.filter(|(dist, _, path, _)| *dist < path.width) + { + let inset = 0; + + // Try to use the column at the centre of the path for sampling to make them + // flatter + let col_pos = -info.wpos().map(|e| e as f32) + path_nearest; + let col00 = info.col(info.wpos() + col_pos.map(|e| e.floor() as i32) + Vec2::new(0, 0)); + let col10 = info.col(info.wpos() + col_pos.map(|e| e.floor() as i32) + Vec2::new(1, 0)); + let col01 = info.col(info.wpos() + col_pos.map(|e| e.floor() as i32) + Vec2::new(0, 1)); + let col11 = info.col(info.wpos() + col_pos.map(|e| e.floor() as i32) + Vec2::new(1, 1)); + let col_attr = |col: &ColumnSample| { + Vec3::new(col.riverless_alt, col.alt, col.water_dist.unwrap_or(1000.0)) }; - let surface_z = col_sample.riverless_alt.floor() as i32; + let [riverless_alt, alt, water_dist] = match (col00, col10, col01, col11) { + (Some(col00), Some(col10), Some(col01), Some(col11)) => Lerp::lerp( + Lerp::lerp(col_attr(col00), col_attr(col10), path_nearest.x.fract()), + Lerp::lerp(col_attr(col01), col_attr(col11), path_nearest.x.fract()), + path_nearest.y.fract(), + ), + _ => col_attr(col), + } + .into_array(); + let (bridge_offset, depth) = ( + ((water_dist.max(0.0) * 0.2).min(f32::consts::PI).cos() + 1.0) * 5.0, + ((1.0 - ((water_dist + 2.0) * 0.3).min(0.0).cos().abs()) + * (riverless_alt + 5.0 - alt).max(0.0) + * 1.75 + + 3.0) as i32, + ); + let surface_z = (riverless_alt + bridge_offset).floor() as i32; - let noisy_color = |col: Rgb, factor: u32| { - let nz = RandomField::new(0).get(Vec3::new(wpos2d.x, wpos2d.y, surface_z)); - col.map(|e| { - (e as u32 + nz % (factor * 2)) - .saturating_sub(factor) - .min(255) as u8 - }) - }; - - if let Some((path_dist, path_nearest, path, _)) = col_sample - .path - .filter(|(dist, _, path, _)| *dist < path.width) - { - let inset = 0; - - // Try to use the column at the centre of the path for sampling to make them - // flatter - let col_pos = (offs - wpos2d).map(|e| e as f32) + path_nearest; - let col00 = info.col(info.wpos() + col_pos.map(|e| e.floor() as i32) + Vec2::new(0, 0)); - let col10 = info.col(info.wpos() + col_pos.map(|e| e.floor() as i32) + Vec2::new(1, 0)); - let col01 = info.col(info.wpos() + col_pos.map(|e| e.floor() as i32) + Vec2::new(0, 1)); - let col11 = info.col(info.wpos() + col_pos.map(|e| e.floor() as i32) + Vec2::new(1, 1)); - let col_attr = |col: &ColumnSample| { - Vec3::new(col.riverless_alt, col.alt, col.water_dist.unwrap_or(1000.0)) - }; - let [riverless_alt, alt, water_dist] = match (col00, col10, col01, col11) { - (Some(col00), Some(col10), Some(col01), Some(col11)) => Lerp::lerp( - Lerp::lerp(col_attr(col00), col_attr(col10), path_nearest.x.fract()), - Lerp::lerp(col_attr(col01), col_attr(col11), path_nearest.x.fract()), - path_nearest.y.fract(), - ), - _ => col_attr(col_sample), - } - .into_array(); - let (bridge_offset, depth) = ( - ((water_dist.max(0.0) * 0.2).min(f32::consts::PI).cos() + 1.0) * 5.0, - ((1.0 - ((water_dist + 2.0) * 0.3).min(0.0).cos().abs()) - * (riverless_alt + 5.0 - alt).max(0.0) - * 1.75 - + 3.0) as i32, + for z in inset - depth..inset { + let _ = canvas.set( + Vec3::new(wpos2d.x, wpos2d.y, surface_z + z), + if bridge_offset >= 2.0 && path_dist >= 3.0 || z < inset - 1 { + Block::new( + BlockKind::Rock, + noisy_color(info.index().colors.layer.bridge.into(), 8), + ) + } else { + let path_color = + path.surface_color(col.sub_surface_color.map(|e| (e * 255.0) as u8)); + Block::new(BlockKind::Earth, noisy_color(path_color, 8)) + }, ); - let surface_z = (riverless_alt + bridge_offset).floor() as i32; - - for z in inset - depth..inset { - let _ = canvas.set( - Vec3::new(wpos2d.x, wpos2d.y, surface_z + z), - if bridge_offset >= 2.0 && path_dist >= 3.0 || z < inset - 1 { - Block::new( - BlockKind::Rock, - noisy_color(info.index().colors.layer.bridge.into(), 8), - ) - } else { - let path_color = path.surface_color( - col_sample.sub_surface_color.map(|e| (e * 255.0) as u8), - ); - Block::new(BlockKind::Earth, noisy_color(path_color, 8)) - }, - ); - } - let head_space = path.head_space(path_dist); - for z in inset..inset + head_space { - let pos = Vec3::new(wpos2d.x, wpos2d.y, surface_z + z); - if canvas.get(pos).unwrap().kind() != BlockKind::Water { - let _ = canvas.set(pos, EMPTY_AIR); - } + } + let head_space = path.head_space(path_dist); + for z in inset..inset + head_space { + let pos = Vec3::new(wpos2d.x, wpos2d.y, surface_z + z); + if canvas.get(pos).unwrap().kind() != BlockKind::Water { + let _ = canvas.set(pos, EMPTY_AIR); } } } - } + }); } -pub fn apply_caves_to<'a>( - wpos2d: Vec2, - mut get_column: impl FnMut(Vec2) -> Option<&'a ColumnSample<'a>>, - vol: &mut (impl BaseVol + RectSizedVol + ReadVol + WriteVol), - index: IndexRef, -) { - for y in 0..vol.size_xy().y as i32 { - for x in 0..vol.size_xy().x as i32 { - let offs = Vec2::new(x, y); +pub fn apply_caves_to<'a>(canvas: &mut Canvas) { + let info = canvas.info(); + canvas.foreach_col(|canvas, wpos2d, col| { + let surface_z = col.riverless_alt.floor() as i32; - let wpos2d = wpos2d + offs; + if let Some((cave_dist, _, cave, _)) = + col.cave.filter(|(dist, _, cave, _)| *dist < cave.width) + { + let cave_x = (cave_dist / cave.width).min(1.0); - // Sample terrain - let col_sample = if let Some(col_sample) = get_column(offs) { - col_sample - } else { - continue; - }; - let surface_z = col_sample.riverless_alt.floor() as i32; + // Relative units + let cave_floor = 0.0 - 0.5 * (1.0 - cave_x.powf(2.0)).max(0.0).sqrt() * cave.width; + let cave_height = (1.0 - cave_x.powf(2.0)).max(0.0).sqrt() * cave.width; - if let Some((cave_dist, _, cave, _)) = col_sample - .cave - .filter(|(dist, _, cave, _)| *dist < cave.width) - { - let cave_x = (cave_dist / cave.width).min(1.0); + // Abs units + let cave_base = (cave.alt + cave_floor) as i32; + let cave_roof = (cave.alt + cave_height) as i32; - // Relative units - let cave_floor = 0.0 - 0.5 * (1.0 - cave_x.powf(2.0)).max(0.0).sqrt() * cave.width; - let cave_height = (1.0 - cave_x.powf(2.0)).max(0.0).sqrt() * cave.width; - - // Abs units - let cave_base = (cave.alt + cave_floor) as i32; - let cave_roof = (cave.alt + cave_height) as i32; - - for z in cave_base..cave_roof { - if cave_x < 0.95 - || index.noise.cave_nz.get( - Vec3::new(wpos2d.x, wpos2d.y, z) - .map(|e| e as f64 * 0.15) - .into_array(), - ) < 0.0 - { - let _ = vol.set(Vec3::new(offs.x, offs.y, z), EMPTY_AIR); - } - } - - // Stalagtites - let stalagtites = index - .noise - .cave_nz - .get(wpos2d.map(|e| e as f64 * 0.125).into_array()) - .sub(0.5) - .max(0.0) - .mul( - (col_sample.alt - cave_roof as f32 - 5.0) - .mul(0.15) - .clamped(0.0, 1.0) as f64, - ) - .mul(45.0) as i32; - - for z in cave_roof - stalagtites..cave_roof { - let _ = vol.set( - Vec3::new(offs.x, offs.y, z), - Block::new(BlockKind::WeakRock, index.colors.layer.stalagtite.into()), - ); - } - - let cave_depth = (col_sample.alt - cave.alt).max(0.0); - let difficulty = cave_depth / 100.0; - - // Scatter things in caves - if RandomField::new(index.seed).chance(wpos2d.into(), 0.001 * difficulty.powf(1.5)) - && cave_base < surface_z as i32 - 25 + for z in cave_base..cave_roof { + if cave_x < 0.95 + || info.index().noise.cave_nz.get( + Vec3::new(wpos2d.x, wpos2d.y, z) + .map(|e| e as f64 * 0.15) + .into_array(), + ) < 0.0 { - let kind = *Lottery::::load_expect("common.cave_scatter") - .choose_seeded(RandomField::new(index.seed + 1).get(wpos2d.into())); - let _ = vol.map(Vec3::new(offs.x, offs.y, cave_base), |block| { - block.with_sprite(kind) + // If the block a little above is liquid, we should stop carving out the cave in + // order to leave a ceiling, and not floating water + if canvas + .get(Vec3::new(wpos2d.x, wpos2d.y, z + 2)) + .map(|b| b.is_liquid()) + .unwrap_or(false) + { + break; + } + + canvas.map(Vec3::new(wpos2d.x, wpos2d.y, z), |b| { + if b.is_liquid() { b } else { EMPTY_AIR } }); } } + + // Stalagtites + let stalagtites = info + .index() + .noise + .cave_nz + .get(wpos2d.map(|e| e as f64 * 0.125).into_array()) + .sub(0.5) + .max(0.0) + .mul( + (col.alt - cave_roof as f32 - 5.0) + .mul(0.15) + .clamped(0.0, 1.0) as f64, + ) + .mul(45.0) as i32; + + for z in cave_roof - stalagtites..cave_roof { + canvas.set( + Vec3::new(wpos2d.x, wpos2d.y, z), + Block::new( + BlockKind::WeakRock, + info.index().colors.layer.stalagtite.into(), + ), + ); + } + + let cave_depth = (col.alt - cave.alt).max(0.0); + let difficulty = cave_depth / 100.0; + + // Scatter things in caves + if RandomField::new(info.index().seed) + .chance(wpos2d.into(), 0.001 * difficulty.powf(1.5)) + && cave_base < surface_z as i32 - 25 + { + let kind = *Lottery::::load_expect("common.cave_scatter") + .choose_seeded(RandomField::new(info.index().seed + 1).get(wpos2d.into())); + canvas.map(Vec3::new(wpos2d.x, wpos2d.y, cave_base), |block| { + block.with_sprite(kind) + }); + } } - } + }); } #[allow(clippy::eval_order_dependence)] pub fn apply_caves_supplement<'a>( diff --git a/world/src/layer/scatter.rs b/world/src/layer/scatter.rs index 2a3746a114..d5a533b7f8 100644 --- a/world/src/layer/scatter.rs +++ b/world/src/layer/scatter.rs @@ -1,8 +1,5 @@ -use crate::{column::ColumnSample, sim::SimChunk, util::RandomField, IndexRef, CONFIG}; -use common::{ - terrain::{Block, SpriteKind}, - vol::{BaseVol, ReadVol, RectSizedVol, WriteVol}, -}; +use crate::{column::ColumnSample, sim::SimChunk, util::RandomField, Canvas, CONFIG}; +use common::terrain::SpriteKind; use noise::NoiseFn; use std::f32; use vek::*; @@ -11,13 +8,7 @@ fn close(x: f32, tgt: f32, falloff: f32) -> f32 { (1.0 - (x - tgt).abs() / falloff).max(0.0).powf(0.125) } const MUSH_FACT: f32 = 1.0e-4; // To balance everything around the mushroom spawning rate -pub fn apply_scatter_to<'a>( - wpos2d: Vec2, - mut get_column: impl FnMut(Vec2) -> Option<&'a ColumnSample<'a>>, - vol: &mut (impl BaseVol + RectSizedVol + ReadVol + WriteVol), - index: IndexRef, - chunk: &SimChunk, -) { +pub fn apply_scatter_to<'a>(canvas: &mut Canvas) { use SpriteKind::*; #[allow(clippy::type_complexity)] // TODO: Add back all sprites we had before @@ -286,76 +277,65 @@ pub fn apply_scatter_to<'a>( (Chest, true, |_, _| (MUSH_FACT * 0.1, None)), ]; - for y in 0..vol.size_xy().y as i32 { - for x in 0..vol.size_xy().x as i32 { - let offs = Vec2::new(x, y); + canvas.foreach_col(|canvas, wpos2d, col| { + let underwater = col.water_level > col.alt; - let wpos2d = wpos2d + offs; - - // Sample terrain - let col_sample = if let Some(col_sample) = get_column(offs) { - col_sample - } else { - continue; - }; - - let underwater = col_sample.water_level > col_sample.alt; - - let kind = scatter - .iter() - .enumerate() - .find_map(|(i, (kind, is_underwater, f))| { - let (density, patch) = f(chunk, col_sample); - let is_patch = patch - .map(|(wavelen, threshold)| { - index - .noise - .scatter_nz - .get( - wpos2d - .map(|e| e as f64 / wavelen as f64 + i as f64 * 43.0) - .into_array(), - ) - .abs() - > 1.0 - threshold as f64 - }) - .unwrap_or(true); - if density > 0.0 - && is_patch - && RandomField::new(i as u32) - .chance(Vec3::new(wpos2d.x, wpos2d.y, 0), density) - && underwater == *is_underwater - { - Some(*kind) - } else { - None - } - }); - - if let Some(kind) = kind { - let alt = col_sample.alt as i32; - - // Find the intersection between ground and air, if there is one near the - // surface - if let Some(solid_end) = (-4..8) - .find(|z| { - vol.get(Vec3::new(offs.x, offs.y, alt + z)) - .map(|b| b.is_solid()) - .unwrap_or(false) - }) - .and_then(|solid_start| { - (1..8).map(|z| solid_start + z).find(|z| { - vol.get(Vec3::new(offs.x, offs.y, alt + z)) - .map(|b| !b.is_solid()) - .unwrap_or(true) - }) + let kind = scatter + .iter() + .enumerate() + .find_map(|(i, (kind, is_underwater, f))| { + let (density, patch) = f(canvas.chunk(), col); + let is_patch = patch + .map(|(wavelen, threshold)| { + canvas + .index() + .noise + .scatter_nz + .get( + wpos2d + .map(|e| e as f64 / wavelen as f64 + i as f64 * 43.0) + .into_array(), + ) + .abs() + > 1.0 - threshold as f64 }) + .unwrap_or(true); + if density > 0.0 + && is_patch + && RandomField::new(i as u32).chance(Vec3::new(wpos2d.x, wpos2d.y, 0), density) + && underwater == *is_underwater { - let _ = vol.map(Vec3::new(offs.x, offs.y, alt + solid_end), |block| { - block.with_sprite(kind) - }); + Some(*kind) + } else { + None } + }); + + if let Some(kind) = kind { + let alt = col.alt as i32; + + // Find the intersection between ground and air, if there is one near the + // surface + if let Some(solid_end) = (-4..8) + .find(|z| { + canvas + .get(Vec3::new(wpos2d.x, wpos2d.y, alt + z)) + .map(|b| b.is_solid()) + .unwrap_or(false) + }) + .and_then(|solid_start| { + (1..8).map(|z| solid_start + z).find(|z| { + canvas + .get(Vec3::new(wpos2d.x, wpos2d.y, alt + z)) + .map(|b| !b.is_solid()) + .unwrap_or(true) + }) + }) + { + canvas.map(Vec3::new(wpos2d.x, wpos2d.y, alt + solid_end), |block| { + block.with_sprite(kind) + }); } } - } + }); } diff --git a/world/src/layer/tree.rs b/world/src/layer/tree.rs new file mode 100644 index 0000000000..b016dcb31e --- /dev/null +++ b/world/src/layer/tree.rs @@ -0,0 +1,127 @@ +use crate::{ + all::ForestKind, + block::block_from_structure, + column::ColumnGen, + util::{RandomPerm, Sampler, UnitChooser}, + Canvas, CONFIG, +}; +use common::{ + terrain::{ + structure::{Structure, StructureBlock}, + Block, + }, + vol::ReadVol, +}; +use lazy_static::lazy_static; +use std::{collections::HashMap, f32, sync::Arc}; +use vek::*; + +lazy_static! { + pub static ref OAKS: Vec> = Structure::load_group("oaks"); + pub static ref OAK_STUMPS: Vec> = Structure::load_group("oak_stumps"); + pub static ref PINES: Vec> = Structure::load_group("pines"); + pub static ref PALMS: Vec> = Structure::load_group("palms"); + pub static ref SNOW_PINES: Vec> = Structure::load_group("snow_pines"); + pub static ref ACACIAS: Vec> = Structure::load_group("acacias"); + pub static ref FRUIT_TREES: Vec> = Structure::load_group("fruit_trees"); + pub static ref BIRCHES: Vec> = Structure::load_group("birch"); + pub static ref MANGROVE_TREES: Vec> = Structure::load_group("mangrove_trees"); + pub static ref QUIRKY: Vec> = Structure::load_group("quirky"); + pub static ref QUIRKY_DRY: Vec> = Structure::load_group("quirky_dry"); +} + +static MODEL_RAND: RandomPerm = RandomPerm::new(0xDB21C052); +static UNIT_CHOOSER: UnitChooser = UnitChooser::new(0x700F4EC7); +static QUIRKY_RAND: RandomPerm = RandomPerm::new(0xA634460F); + +pub fn apply_trees_to<'a>(canvas: &mut Canvas) { + struct Tree { + pos: Vec3, + model: Arc, + seed: u32, + units: (Vec2, Vec2), + } + + let mut tree_cache = HashMap::new(); + + let info = canvas.info(); + canvas.foreach_col(|canvas, wpos2d, col| { + let trees = info.land().get_near_trees(wpos2d); + + for (tree_wpos, seed) in trees { + let tree = if let Some(tree) = tree_cache.entry(tree_wpos).or_insert_with(|| { + let col = ColumnGen::new(info.land()).get((tree_wpos, info.index()))?; + + // Ensure that it's valid to place a tree here + if ((seed.wrapping_mul(13)) & 0xFF) as f32 / 256.0 > col.tree_density + || col.alt < col.water_level + || col.spawn_rate < 0.5 + || col.water_dist.map(|d| d < 8.0).unwrap_or(false) + || col.path.map(|(d, _, _, _)| d < 12.0).unwrap_or(false) + { + return None; + } + + Some(Tree { + pos: Vec3::new(tree_wpos.x, tree_wpos.y, col.alt as i32), + model: { + let models: &'static [_] = if QUIRKY_RAND.get(seed) % 512 == 17 { + if col.temp > CONFIG.desert_temp { + &QUIRKY_DRY + } else { + &QUIRKY + } + } else { + match col.forest_kind { + ForestKind::Palm => &PALMS, + ForestKind::Savannah => &ACACIAS, + ForestKind::Oak if QUIRKY_RAND.get(seed) % 16 == 7 => &OAK_STUMPS, + ForestKind::Oak if QUIRKY_RAND.get(seed) % 19 == 7 => &FRUIT_TREES, + ForestKind::Oak if QUIRKY_RAND.get(seed) % 14 == 7 => &BIRCHES, + ForestKind::Oak => &OAKS, + ForestKind::Pine => &PINES, + ForestKind::SnowPine => &SNOW_PINES, + ForestKind::Mangrove => &MANGROVE_TREES, + } + }; + models[(MODEL_RAND.get(seed.wrapping_mul(17)) / 13) as usize % models.len()] + .clone() + }, + seed, + units: UNIT_CHOOSER.get(seed), + }) + }) { + tree + } else { + continue; + }; + + let bounds = tree.model.get_bounds(); + for z in bounds.min.z..bounds.max.z { + let wpos = Vec3::new(wpos2d.x, wpos2d.y, tree.pos.z + z); + let model_pos = Vec3::from( + (wpos - tree.pos) + .xy() + .map2(Vec2::new(tree.units.0, tree.units.1), |rpos, unit| { + unit * rpos + }) + .sum(), + ) + Vec3::unit_z() * (wpos.z - tree.pos.z); + block_from_structure( + info.index(), + tree.model + .get(model_pos) + .ok() + .copied() + .unwrap_or(StructureBlock::None), + wpos, + tree.pos.xy(), + tree.seed, + col, + Block::air, + ) + .map(|block| canvas.set(wpos, block)); + } + } + }); +} diff --git a/world/src/lib.rs b/world/src/lib.rs index d52a39c3c1..9f6812dfed 100644 --- a/world/src/lib.rs +++ b/world/src/lib.rs @@ -8,7 +8,8 @@ const_generics, const_panic, label_break_value, - or_patterns + or_patterns, + array_value_iter )] mod all; @@ -27,8 +28,8 @@ pub mod util; // Reexports pub use crate::{ - config::CONFIG, canvas::{Canvas, CanvasInfo}, + config::CONFIG, }; pub use block::BlockGen; pub use column::ColumnSample; @@ -171,8 +172,7 @@ impl World { _ => continue, }; - let (min_z, only_structures_min_z, max_z) = - z_cache.get_z_limits(&mut sampler, index); + let (min_z, max_z) = z_cache.get_z_limits(); (base_z..min_z as i32).for_each(|z| { let _ = chunk.set(Vec3::new(x, y, z), stone); @@ -181,11 +181,8 @@ impl World { (min_z as i32..max_z as i32).for_each(|z| { let lpos = Vec3::new(x, y, z); let wpos = Vec3::from(chunk_wpos2d) + lpos; - let only_structures = lpos.z >= only_structures_min_z as i32; - if let Some(block) = - sampler.get_with_z_cache(wpos, Some(&z_cache), only_structures, index) - { + if let Some(block) = sampler.get_with_z_cache(wpos, Some(&z_cache)) { let _ = chunk.set(lpos, block); } }); @@ -204,21 +201,22 @@ impl World { let mut dynamic_rng = rand::thread_rng(); // Apply layers (paths, caves, etc.) - layer::apply_caves_to(chunk_wpos2d, sample_get, &mut chunk, index); - layer::apply_scatter_to(chunk_wpos2d, sample_get, &mut chunk, index, sim_chunk); - - let canvas_info = CanvasInfo { - wpos: chunk_pos * TerrainChunkSize::RECT_SIZE.map(|e| e as i32), - column_grid: &zcache_grid, - column_grid_border: grid_border, - index, - }; let mut canvas = Canvas { - wpos: chunk_pos * TerrainChunkSize::RECT_SIZE.map(|e| e as i32), + info: CanvasInfo { + wpos: chunk_pos * TerrainChunkSize::RECT_SIZE.map(|e| e as i32), + column_grid: &zcache_grid, + column_grid_border: grid_border, + land: &self.sim, + index, + chunk: sim_chunk, + }, chunk: &mut chunk, }; - layer::apply_paths_to(&mut canvas, &canvas_info); + layer::apply_trees_to(&mut canvas); + layer::apply_scatter_to(&mut canvas); + layer::apply_caves_to(&mut canvas); + layer::apply_paths_to(&mut canvas); // Apply site generation sim_chunk.sites.iter().for_each(|site| { diff --git a/world/src/sim/mod.rs b/world/src/sim/mod.rs index 5f67f0712b..5adf44d64d 100644 --- a/world/src/sim/mod.rs +++ b/world/src/sim/mod.rs @@ -1431,25 +1431,11 @@ impl WorldSim { .into_par_iter() .map_init( || Box::new(BlockGen::new(ColumnGen::new(self))), - |block_gen, posi| { - let wpos = uniform_idx_as_vec2(self.map_size_lg(), posi); - let mut sample = column_sample.get( + |_block_gen, posi| { + let sample = column_sample.get( (uniform_idx_as_vec2(self.map_size_lg(), posi) * TerrainChunkSize::RECT_SIZE.map(|e| e as i32), index) )?; - let alt = sample.alt; - /* let z_cache = block_gen.get_z_cache(wpos); - sample.alt = alt.max(z_cache.get_z_limits(&mut block_gen).2); */ - sample.alt = alt.max(BlockGen::get_cliff_height( - &block_gen.column_gen, - &mut block_gen.column_cache, - wpos.map(|e| e as f32), - &sample.close_cliffs, - sample.cliff_hill, - 32.0, - index, - )); - sample.basement += sample.alt - alt; // sample.water_level = CONFIG.sea_level.max(sample.water_level); Some(sample) @@ -1997,6 +1983,14 @@ impl WorldSim { pub fn get_nearest_cave(&self, wpos: Vec2) -> Option<(f32, Vec2, Cave, Vec2)> { self.get_nearest_way(wpos, |chunk| Some(chunk.cave)) } + + /// Return an iterator over candidate tree positions (note that only some of + /// these will become trees since environmental parameters may forbid + /// them spawning). + pub fn get_near_trees(&self, wpos: Vec2) -> impl Iterator, u32)> + '_ { + // Deterministic based on wpos + std::array::IntoIter::new(self.gen_ctx.structure_gen.get(wpos)) + } } #[derive(Debug)]