Turned tree generation into a post-processing layer, ripped out old tree generator for performance wins

This commit is contained in:
Joshua Barretto 2020-11-07 21:50:52 +00:00
parent 647dcb2fec
commit 9b233708e2
11 changed files with 506 additions and 825 deletions

View File

@ -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))); float ndist = step_to_dist(trunc(dist_to_step(cdist - 0.25)));
vec3 sample = cloud_at(origin + (dir + dir_diff / ndist) * ndist * splay, ndist); 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 sun_access = sample.x;
float scatter_factor = 1.0 - 1.0 / (1.0 + density_integrals.x); float scatter_factor = 1.0 - 1.0 / (1.0 + density_integrals.x);

View File

@ -281,7 +281,7 @@ impl Server {
.expect(&format!("no z_cache found for chunk: {}", spawn_chunk)); .expect(&format!("no z_cache found for chunk: {}", spawn_chunk));
// get the minimum and maximum z values at which there could be solid blocks // 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 // round range outwards, so no potential air block is missed
let min_z = min_z.floor() as i32; let min_z = min_z.floor() as i32;
let max_z = max_z.ceil() as i32; let max_z = max_z.ceil() as i32;
@ -296,8 +296,6 @@ impl Server {
.get_with_z_cache( .get_with_z_cache(
Vec3::new(spawn_location.x, spawn_location.y, *z), Vec3::new(spawn_location.x, spawn_location.y, *z),
Some(&z_cache), Some(&z_cache),
false,
index,
) )
.map(|b| b.is_air()) .map(|b| b.is_air())
.unwrap_or(false) .unwrap_or(false)

View File

@ -1,16 +1,11 @@
mod natural;
use crate::{ use crate::{
column::{ColumnGen, ColumnSample}, column::{ColumnGen, ColumnSample},
util::{RandomField, Sampler, SmallCache}, util::{RandomField, Sampler, SmallCache},
IndexRef, IndexRef,
}; };
use common::{ use common::terrain::{
terrain::{ structure::{self, StructureBlock},
structure::{self, StructureBlock}, Block, BlockKind, SpriteKind,
Block, BlockKind, SpriteKind, Structure,
},
vol::ReadVol,
}; };
use core::ops::{Div, Mul, Range}; use core::ops::{Div, Mul, Range};
use serde::Deserialize; use serde::Deserialize;
@ -18,7 +13,6 @@ use vek::*;
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct Colors { pub struct Colors {
pub pyramid: (u8, u8, u8),
// TODO(@Sharp): After the merge, construct enough infrastructure to make it convenient to // 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 // define mapping functions over the input; i.e. we should be able to interpret some fields as
// defining App<Abs<Fun, Type>, Arg>, where Fun : (Context, Arg) → (S, Type). // defining App<Abs<Fun, Type>, Arg>, where Fun : (Context, Arg) → (S, Type).
@ -26,17 +20,11 @@ pub struct Colors {
} }
pub struct BlockGen<'a> { pub struct BlockGen<'a> {
pub column_cache: SmallCache<Option<ColumnSample<'a>>>,
pub column_gen: ColumnGen<'a>, pub column_gen: ColumnGen<'a>,
} }
impl<'a> BlockGen<'a> { impl<'a> BlockGen<'a> {
pub fn new(column_gen: ColumnGen<'a>) -> Self { pub fn new(column_gen: ColumnGen<'a>) -> Self { Self { column_gen } }
Self {
column_cache: SmallCache::default(),
column_gen,
}
}
pub fn sample_column<'b>( pub fn sample_column<'b>(
column_gen: &ColumnGen<'a>, column_gen: &ColumnGen<'a>,
@ -49,119 +37,17 @@ impl<'a> BlockGen<'a> {
.as_ref() .as_ref()
} }
pub fn get_cliff_height(
column_gen: &ColumnGen<'a>,
cache: &mut SmallCache<Option<ColumnSample<'a>>>,
wpos: Vec2<f32>,
close_cliffs: &[(Vec2<i32>, 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<i32>, index: IndexRef<'a>) -> Option<ZCache<'a>> { pub fn get_z_cache(&mut self, wpos: Vec2<i32>, index: IndexRef<'a>) -> Option<ZCache<'a>> {
let BlockGen { let BlockGen { column_gen } = self;
column_cache,
column_gen,
} = self;
// Main sample // Main sample
let sample = column_gen.get((wpos, index))?; let sample = column_gen.get((wpos, index))?;
// Tree samples Some(ZCache { sample })
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,
})
} }
pub fn get_with_z_cache( pub fn get_with_z_cache(&mut self, wpos: Vec3<i32>, z_cache: Option<&ZCache>) -> Option<Block> {
&mut self, let BlockGen { column_gen } = self;
wpos: Vec3<i32>,
z_cache: Option<&ZCache>,
only_structures: bool,
index: IndexRef<'a>,
) -> Option<Block> {
let BlockGen {
column_cache,
column_gen,
} = self;
let world = column_gen.sim; let world = column_gen.sim;
let z_cache = z_cache?; let z_cache = z_cache?;
@ -180,300 +66,133 @@ impl<'a> BlockGen<'a> {
// marble, // marble,
// marble_small, // marble_small,
rock, rock,
//cliffs,
cliff_hill,
close_cliffs,
// temp, // temp,
// humidity, // humidity,
stone_col, stone_col,
.. ..
} = sample; } = sample;
let structures = &z_cache.structures;
let wposf = wpos.map(|e| e as f64); let wposf = wpos.map(|e| e as f64);
let (block, _height) = if !only_structures { let (_definitely_underground, height, basement_height, water_height) =
let (_definitely_underground, height, _on_cliff, basement_height, water_height) = if (wposf.z as f32) < alt - 64.0 * chaos {
if (wposf.z as f32) < alt - 64.0 * chaos { // Shortcut warping
// Shortcut warping (true, alt, basement, water_level)
(true, alt, false, basement, water_level) } else {
} else { // Apply warping
// Apply warping let warp = world
let warp = world .gen_ctx
.gen_ctx .warp_nz
.warp_nz .get(wposf.div(24.0))
.get(wposf.div(24.0)) .mul((chaos - 0.1).max(0.0).min(1.0).powf(2.0))
.mul((chaos - 0.1).max(0.0).min(1.0).powf(2.0)) .mul(16.0);
.mul(16.0); let warp = Lerp::lerp(0.0, warp, warp_factor);
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 false,
(surface_height, false) height,
basement + height - alt,
(if water_level <= alt {
water_level + warp
} else { } else {
let turb = Vec2::new( water_level
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,
) )
.map(|e| (e * 255.0) as u8); };
if stone_factor >= 0.5 { // Sample blocks
Some(Block::new(BlockKind::Rock, col))
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 { } else {
Some(Block::new(BlockKind::Earth, col)) BlockKind::Earth
} },
} else if (wposf.z as f32) < height { col.map(|e| (e * 255.0) as u8),
let grass_factor = (wposf.z as f32 - (height - grass_depth)) ))
.div(grass_depth) } else {
.powf(0.5); None
let col = Lerp::lerp(sub_surface_color, surface_color, grass_factor); }
// Surface .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( Some(Block::new(
if grass_factor > 0.7 { BlockKind::WeakRock,
BlockKind::Grass stone_col.map2(
} else { Rgb::new(
BlockKind::Earth field0.get(wpos) as u8 % 16,
}, field1.get(wpos) as u8 % 16,
col.map(|e| (e * 255.0) as u8), field2.get(wpos) as u8 % 16,
),
|stone, x| stone.saturating_sub(x),
),
)) ))
} else { } else {
None None
} }
.or_else(|| { })
// Rocks .or_else(|| {
if (height + 2.5 - wposf.z as f32).div(7.5).abs().powf(2.0) < rock { // Water
#[allow(clippy::identity_op)] if (wposf.z as f32) < water_height {
let field0 = RandomField::new(world.seed + 0); // Ocean
let field1 = RandomField::new(world.seed + 1); Some(water)
let field2 = RandomField::new(world.seed + 2); } else {
None
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);
block block
} }
} }
pub struct ZCache<'a> { pub struct ZCache<'a> {
wpos: Vec2<i32>,
pub sample: ColumnSample<'a>, pub sample: ColumnSample<'a>,
structures: [Option<(StructureInfo, ColumnSample<'a>)>; 9],
} }
impl<'a> ZCache<'a> { impl<'a> ZCache<'a> {
pub fn get_z_limits<'b>( pub fn get_z_limits(&self) -> (f32, f32) {
&self,
block_gen: &mut BlockGen<'b>,
index: IndexRef<'b>,
) -> (f32, f32, f32) {
let min = self.sample.alt - (self.sample.chaos.min(1.0) * 16.0); let min = self.sample.alt - (self.sample.chaos.min(1.0) * 16.0);
let min = min - 4.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 rocks = if self.sample.rock > 0.0 { 12.0 } else { 0.0 };
let warp = self.sample.chaos * 32.0; let warp = self.sample.chaos * 32.0;
let (structure_min, structure_max) = self let ground_max = self.sample.alt + warp + rocks + 2.0;
.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),
};
if st_area.contains_point(self.wpos - st_info.pos) { let max = ground_max.max(self.sample.water_level + 2.0);
(min.min(bounds.min.z as f32), max.max(bounds.max.z as f32))
} else {
(min, max)
}
});
let ground_max = (self.sample.alt + warp + rocks).max(cliff) + 2.0; (min, max)
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<i32>, Vec2<i32>),
volume: &'static Structure,
},
}
pub struct StructureInfo {
pos: Vec3<i32>,
seed: u32,
meta: StructureMeta,
}
impl StructureInfo {
fn get_bounds(&self) -> Aabb<i32> {
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<i32>, sample: &ColumnSample) -> Option<Block> {
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,
)
})
},
}
} }
} }

View File

@ -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<Option<ColumnSample<'a>>>,
st_pos: Vec2<i32>,
st_seed: u32,
st_sample: &ColumnSample,
index: IndexRef<'a>,
) -> Option<StructureInfo> {
// 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<Arc<Structure>> = Structure::load_group("oaks");
pub static ref OAK_STUMPS: Vec<Arc<Structure>> = Structure::load_group("oak_stumps");
pub static ref PINES: Vec<Arc<Structure>> = Structure::load_group("pines");
pub static ref PALMS: Vec<Arc<Structure>> = Structure::load_group("palms");
pub static ref SNOW_PINES: Vec<Arc<Structure>> = Structure::load_group("snow_pines");
pub static ref ACACIAS: Vec<Arc<Structure>> = Structure::load_group("acacias");
pub static ref FRUIT_TREES: Vec<Arc<Structure>> = Structure::load_group("fruit_trees");
pub static ref BIRCHES: Vec<Arc<Structure>> = Structure::load_group("birch");
pub static ref MANGROVE_TREES: Vec<Arc<Structure>> = Structure::load_group("mangrove_trees");
pub static ref QUIRKY: Vec<Arc<Structure>> = Structure::load_group("quirky");
pub static ref QUIRKY_DRY: Vec<Arc<Structure>> = Structure::load_group("quirky_dry");
}

View File

@ -1,32 +1,39 @@
use vek::*;
use common::{
terrain::{TerrainChunk, Block, TerrainChunkSize},
vol::{ReadVol, WriteVol, RectVolSize},
};
use crate::{ use crate::{
block::ZCache, block::ZCache,
util::Grid,
column::ColumnSample, column::ColumnSample,
index::IndexRef, 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 struct CanvasInfo<'a> {
pub(crate) wpos: Vec2<i32>, pub(crate) wpos: Vec2<i32>,
pub(crate) column_grid: &'a Grid<Option<ZCache<'a>>>, pub(crate) column_grid: &'a Grid<Option<ZCache<'a>>>,
pub(crate) column_grid_border: i32, pub(crate) column_grid_border: i32,
pub(crate) land: &'a Land,
pub(crate) index: IndexRef<'a>, pub(crate) index: IndexRef<'a>,
pub(crate) chunk: &'a SimChunk,
} }
impl<'a> CanvasInfo<'a> { impl<'a> CanvasInfo<'a> {
pub fn wpos(&self) -> Vec2<i32> { pub fn wpos(&self) -> Vec2<i32> { self.wpos }
self.wpos
}
pub fn area(&self) -> Aabr<i32> { pub fn area(&self) -> Aabr<i32> {
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<i32>) -> Option<&ColumnSample> { pub fn col(&self, pos: Vec2<i32>) -> Option<&'a ColumnSample> {
self.column_grid self.column_grid
.get(self.column_grid_border + pos - self.wpos()) .get(self.column_grid_border + pos - self.wpos())
.map(Option::as_ref) .map(Option::as_ref)
@ -34,24 +41,24 @@ impl<'a> CanvasInfo<'a> {
.map(|zc| &zc.sample) .map(|zc| &zc.sample)
} }
pub fn index(&self) -> IndexRef { pub fn index(&self) -> IndexRef<'a> { self.index }
self.index
} pub fn chunk(&self) -> &'a SimChunk { self.chunk }
pub fn land(&self) -> &'a Land { self.land }
} }
pub struct Canvas<'a> { pub struct Canvas<'a> {
pub(crate) wpos: Vec2<i32>, pub(crate) info: CanvasInfo<'a>,
pub(crate) chunk: &'a mut TerrainChunk, pub(crate) chunk: &'a mut TerrainChunk,
} }
impl<'a> Canvas<'a> { impl<'a> Canvas<'a> {
pub fn wpos(&self) -> Vec2<i32> { /// The borrow checker complains at immutable features of canvas (column
self.wpos /// 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 area(&self) -> Aabr<i32> { pub fn info(&mut self) -> CanvasInfo<'a> { self.info }
Rect::from((self.wpos(), Extent2::from(TerrainChunkSize::RECT_SIZE.map(|e| e as i32)))).into()
}
pub fn get(&mut self, pos: Vec3<i32>) -> Option<Block> { pub fn get(&mut self, pos: Vec3<i32>) -> Option<Block> {
self.chunk.get(pos - self.wpos()).ok().copied() self.chunk.get(pos - self.wpos()).ok().copied()
@ -60,4 +67,30 @@ impl<'a> Canvas<'a> {
pub fn set(&mut self, pos: Vec3<i32>, block: Block) { pub fn set(&mut self, pos: Vec3<i32>, block: Block) {
let _ = self.chunk.set(pos - self.wpos(), block); let _ = self.chunk.set(pos - self.wpos(), block);
} }
pub fn map(&mut self, pos: Vec3<i32>, 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<i32>, &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 }
} }

View File

@ -1,6 +1,5 @@
use crate::{ use crate::{
all::ForestKind, all::ForestKind,
block::StructureMeta,
sim::{local_cells, Cave, Path, RiverKind, SimChunk, WorldSim}, sim::{local_cells, Cave, Path, RiverKind, SimChunk, WorldSim},
util::Sampler, util::Sampler,
IndexRef, CONFIG, IndexRef, CONFIG,
@ -54,56 +53,6 @@ pub struct Colors {
impl<'a> ColumnGen<'a> { impl<'a> ColumnGen<'a> {
pub fn new(sim: &'a WorldSim) -> Self { Self { sim } } 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<i32>) -> Option<StructureData> {
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<i32>) -> [Option<StructureData>; 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> { impl<'a> Sampler<'a> for ColumnGen<'a> {
@ -1053,7 +1002,6 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
0.0 0.0
}, },
forest_kind: sim_chunk.forest_kind, forest_kind: sim_chunk.forest_kind,
close_structures: self.gen_close_structures(wpos),
marble, marble,
marble_small, marble_small,
rock, rock,
@ -1086,7 +1034,6 @@ pub struct ColumnSample<'a> {
pub sub_surface_color: Rgb<f32>, pub sub_surface_color: Rgb<f32>,
pub tree_density: f32, pub tree_density: f32,
pub forest_kind: ForestKind, pub forest_kind: ForestKind,
pub close_structures: [Option<StructureData>; 9],
pub marble: f32, pub marble: f32,
pub marble_small: f32, pub marble_small: f32,
pub rock: f32, pub rock: f32,
@ -1104,10 +1051,3 @@ pub struct ColumnSample<'a> {
pub chunk: &'a SimChunk, pub chunk: &'a SimChunk,
} }
#[derive(Copy, Clone)]
pub struct StructureData {
pub pos: Vec2<i32>,
pub seed: u32,
pub meta: Option<StructureMeta>,
}

View File

@ -1,13 +1,12 @@
pub mod scatter; 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::{ use crate::{
column::ColumnSample, column::ColumnSample,
util::{RandomField, Sampler}, util::{RandomField, Sampler},
IndexRef, Canvas, IndexRef,
Canvas,
CanvasInfo,
}; };
use common::{ use common::{
assets::Asset, assets::Asset,
@ -34,178 +33,162 @@ pub struct Colors {
const EMPTY_AIR: Block = Block::air(SpriteKind::Empty); const EMPTY_AIR: Block = Block::air(SpriteKind::Empty);
pub fn apply_paths_to<'a>( pub fn apply_paths_to<'a>(canvas: &mut Canvas) {
canvas: &mut Canvas, let info = canvas.info();
info: &CanvasInfo, canvas.foreach_col(|canvas, wpos2d, col| {
) { let surface_z = col.riverless_alt.floor() as i32;
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);
let wpos2d = canvas.wpos() + offs; let noisy_color = |color: Rgb<u8>, 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 if let Some((path_dist, path_nearest, path, _)) =
let col_sample = if let Some(col_sample) = info.col(wpos2d) { col.path.filter(|(dist, _, path, _)| *dist < path.width)
col_sample {
} else { let inset = 0;
continue;
// 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<u8>, factor: u32| { for z in inset - depth..inset {
let nz = RandomField::new(0).get(Vec3::new(wpos2d.x, wpos2d.y, surface_z)); let _ = canvas.set(
col.map(|e| { Vec3::new(wpos2d.x, wpos2d.y, surface_z + z),
(e as u32 + nz % (factor * 2)) if bridge_offset >= 2.0 && path_dist >= 3.0 || z < inset - 1 {
.saturating_sub(factor) Block::new(
.min(255) as u8 BlockKind::Rock,
}) noisy_color(info.index().colors.layer.bridge.into(), 8),
}; )
} else {
if let Some((path_dist, path_nearest, path, _)) = col_sample let path_color =
.path path.surface_color(col.sub_surface_color.map(|e| (e * 255.0) as u8));
.filter(|(dist, _, path, _)| *dist < path.width) Block::new(BlockKind::Earth, noisy_color(path_color, 8))
{ },
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,
); );
let surface_z = (riverless_alt + bridge_offset).floor() as i32; }
let head_space = path.head_space(path_dist);
for z in inset - depth..inset { for z in inset..inset + head_space {
let _ = canvas.set( let pos = Vec3::new(wpos2d.x, wpos2d.y, surface_z + z);
Vec3::new(wpos2d.x, wpos2d.y, surface_z + z), if canvas.get(pos).unwrap().kind() != BlockKind::Water {
if bridge_offset >= 2.0 && path_dist >= 3.0 || z < inset - 1 { let _ = canvas.set(pos, EMPTY_AIR);
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);
}
} }
} }
} }
} });
} }
pub fn apply_caves_to<'a>( pub fn apply_caves_to<'a>(canvas: &mut Canvas) {
wpos2d: Vec2<i32>, let info = canvas.info();
mut get_column: impl FnMut(Vec2<i32>) -> Option<&'a ColumnSample<'a>>, canvas.foreach_col(|canvas, wpos2d, col| {
vol: &mut (impl BaseVol<Vox = Block> + RectSizedVol + ReadVol + WriteVol), let surface_z = col.riverless_alt.floor() as i32;
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);
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 // Relative units
let col_sample = if let Some(col_sample) = get_column(offs) { let cave_floor = 0.0 - 0.5 * (1.0 - cave_x.powf(2.0)).max(0.0).sqrt() * cave.width;
col_sample let cave_height = (1.0 - cave_x.powf(2.0)).max(0.0).sqrt() * cave.width;
} else {
continue;
};
let surface_z = col_sample.riverless_alt.floor() as i32;
if let Some((cave_dist, _, cave, _)) = col_sample // Abs units
.cave let cave_base = (cave.alt + cave_floor) as i32;
.filter(|(dist, _, cave, _)| *dist < cave.width) let cave_roof = (cave.alt + cave_height) as i32;
{
let cave_x = (cave_dist / cave.width).min(1.0);
// Relative units for z in cave_base..cave_roof {
let cave_floor = 0.0 - 0.5 * (1.0 - cave_x.powf(2.0)).max(0.0).sqrt() * cave.width; if cave_x < 0.95
let cave_height = (1.0 - cave_x.powf(2.0)).max(0.0).sqrt() * cave.width; || info.index().noise.cave_nz.get(
Vec3::new(wpos2d.x, wpos2d.y, z)
// Abs units .map(|e| e as f64 * 0.15)
let cave_base = (cave.alt + cave_floor) as i32; .into_array(),
let cave_roof = (cave.alt + cave_height) as i32; ) < 0.0
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
{ {
let kind = *Lottery::<SpriteKind>::load_expect("common.cave_scatter") // If the block a little above is liquid, we should stop carving out the cave in
.choose_seeded(RandomField::new(index.seed + 1).get(wpos2d.into())); // order to leave a ceiling, and not floating water
let _ = vol.map(Vec3::new(offs.x, offs.y, cave_base), |block| { if canvas
block.with_sprite(kind) .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::<SpriteKind>::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)] #[allow(clippy::eval_order_dependence)]
pub fn apply_caves_supplement<'a>( pub fn apply_caves_supplement<'a>(

View File

@ -1,8 +1,5 @@
use crate::{column::ColumnSample, sim::SimChunk, util::RandomField, IndexRef, CONFIG}; use crate::{column::ColumnSample, sim::SimChunk, util::RandomField, Canvas, CONFIG};
use common::{ use common::terrain::SpriteKind;
terrain::{Block, SpriteKind},
vol::{BaseVol, ReadVol, RectSizedVol, WriteVol},
};
use noise::NoiseFn; use noise::NoiseFn;
use std::f32; use std::f32;
use vek::*; 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) (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 const MUSH_FACT: f32 = 1.0e-4; // To balance everything around the mushroom spawning rate
pub fn apply_scatter_to<'a>( pub fn apply_scatter_to<'a>(canvas: &mut Canvas) {
wpos2d: Vec2<i32>,
mut get_column: impl FnMut(Vec2<i32>) -> Option<&'a ColumnSample<'a>>,
vol: &mut (impl BaseVol<Vox = Block> + RectSizedVol + ReadVol + WriteVol),
index: IndexRef,
chunk: &SimChunk,
) {
use SpriteKind::*; use SpriteKind::*;
#[allow(clippy::type_complexity)] #[allow(clippy::type_complexity)]
// TODO: Add back all sprites we had before // 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)), (Chest, true, |_, _| (MUSH_FACT * 0.1, None)),
]; ];
for y in 0..vol.size_xy().y as i32 { canvas.foreach_col(|canvas, wpos2d, col| {
for x in 0..vol.size_xy().x as i32 { let underwater = col.water_level > col.alt;
let offs = Vec2::new(x, y);
let wpos2d = wpos2d + offs; let kind = scatter
.iter()
// Sample terrain .enumerate()
let col_sample = if let Some(col_sample) = get_column(offs) { .find_map(|(i, (kind, is_underwater, f))| {
col_sample let (density, patch) = f(canvas.chunk(), col);
} else { let is_patch = patch
continue; .map(|(wavelen, threshold)| {
}; canvas
.index()
let underwater = col_sample.water_level > col_sample.alt; .noise
.scatter_nz
let kind = scatter .get(
.iter() wpos2d
.enumerate() .map(|e| e as f64 / wavelen as f64 + i as f64 * 43.0)
.find_map(|(i, (kind, is_underwater, f))| { .into_array(),
let (density, patch) = f(chunk, col_sample); )
let is_patch = patch .abs()
.map(|(wavelen, threshold)| { > 1.0 - threshold as f64
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)
})
}) })
.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| { Some(*kind)
block.with_sprite(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)
});
} }
} }
} });
} }

127
world/src/layer/tree.rs Normal file
View File

@ -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<Arc<Structure>> = Structure::load_group("oaks");
pub static ref OAK_STUMPS: Vec<Arc<Structure>> = Structure::load_group("oak_stumps");
pub static ref PINES: Vec<Arc<Structure>> = Structure::load_group("pines");
pub static ref PALMS: Vec<Arc<Structure>> = Structure::load_group("palms");
pub static ref SNOW_PINES: Vec<Arc<Structure>> = Structure::load_group("snow_pines");
pub static ref ACACIAS: Vec<Arc<Structure>> = Structure::load_group("acacias");
pub static ref FRUIT_TREES: Vec<Arc<Structure>> = Structure::load_group("fruit_trees");
pub static ref BIRCHES: Vec<Arc<Structure>> = Structure::load_group("birch");
pub static ref MANGROVE_TREES: Vec<Arc<Structure>> = Structure::load_group("mangrove_trees");
pub static ref QUIRKY: Vec<Arc<Structure>> = Structure::load_group("quirky");
pub static ref QUIRKY_DRY: Vec<Arc<Structure>> = 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<i32>,
model: Arc<Structure>,
seed: u32,
units: (Vec2<i32>, Vec2<i32>),
}
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));
}
}
});
}

View File

@ -8,7 +8,8 @@
const_generics, const_generics,
const_panic, const_panic,
label_break_value, label_break_value,
or_patterns or_patterns,
array_value_iter
)] )]
mod all; mod all;
@ -27,8 +28,8 @@ pub mod util;
// Reexports // Reexports
pub use crate::{ pub use crate::{
config::CONFIG,
canvas::{Canvas, CanvasInfo}, canvas::{Canvas, CanvasInfo},
config::CONFIG,
}; };
pub use block::BlockGen; pub use block::BlockGen;
pub use column::ColumnSample; pub use column::ColumnSample;
@ -171,8 +172,7 @@ impl World {
_ => continue, _ => continue,
}; };
let (min_z, only_structures_min_z, max_z) = let (min_z, max_z) = z_cache.get_z_limits();
z_cache.get_z_limits(&mut sampler, index);
(base_z..min_z as i32).for_each(|z| { (base_z..min_z as i32).for_each(|z| {
let _ = chunk.set(Vec3::new(x, y, z), stone); 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| { (min_z as i32..max_z as i32).for_each(|z| {
let lpos = Vec3::new(x, y, z); let lpos = Vec3::new(x, y, z);
let wpos = Vec3::from(chunk_wpos2d) + lpos; let wpos = Vec3::from(chunk_wpos2d) + lpos;
let only_structures = lpos.z >= only_structures_min_z as i32;
if let Some(block) = if let Some(block) = sampler.get_with_z_cache(wpos, Some(&z_cache)) {
sampler.get_with_z_cache(wpos, Some(&z_cache), only_structures, index)
{
let _ = chunk.set(lpos, block); let _ = chunk.set(lpos, block);
} }
}); });
@ -204,21 +201,22 @@ impl World {
let mut dynamic_rng = rand::thread_rng(); let mut dynamic_rng = rand::thread_rng();
// Apply layers (paths, caves, etc.) // 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 { 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, 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 // Apply site generation
sim_chunk.sites.iter().for_each(|site| { sim_chunk.sites.iter().for_each(|site| {

View File

@ -1431,25 +1431,11 @@ impl WorldSim {
.into_par_iter() .into_par_iter()
.map_init( .map_init(
|| Box::new(BlockGen::new(ColumnGen::new(self))), || Box::new(BlockGen::new(ColumnGen::new(self))),
|block_gen, posi| { |_block_gen, posi| {
let wpos = uniform_idx_as_vec2(self.map_size_lg(), posi); let sample = column_sample.get(
let mut sample = column_sample.get(
(uniform_idx_as_vec2(self.map_size_lg(), posi) * TerrainChunkSize::RECT_SIZE.map(|e| e as i32), (uniform_idx_as_vec2(self.map_size_lg(), posi) * TerrainChunkSize::RECT_SIZE.map(|e| e as i32),
index) 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); // sample.water_level = CONFIG.sea_level.max(sample.water_level);
Some(sample) Some(sample)
@ -1997,6 +1983,14 @@ impl WorldSim {
pub fn get_nearest_cave(&self, wpos: Vec2<i32>) -> Option<(f32, Vec2<f32>, Cave, Vec2<f32>)> { pub fn get_nearest_cave(&self, wpos: Vec2<i32>) -> Option<(f32, Vec2<f32>, Cave, Vec2<f32>)> {
self.get_nearest_way(wpos, |chunk| Some(chunk.cave)) 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<i32>) -> impl Iterator<Item = (Vec2<i32>, u32)> + '_ {
// Deterministic based on wpos
std::array::IntoIter::new(self.gen_ctx.structure_gen.get(wpos))
}
} }
#[derive(Debug)] #[derive(Debug)]