mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Turned tree generation into a post-processing layer, ripped out old tree generator for performance wins
This commit is contained in:
parent
647dcb2fec
commit
9b233708e2
@ -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);
|
||||
|
@ -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)
|
||||
|
@ -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<Abs<Fun, Type>, Arg>, where Fun : (Context, Arg) → (S, Type).
|
||||
@ -26,17 +20,11 @@ pub struct Colors {
|
||||
}
|
||||
|
||||
pub struct BlockGen<'a> {
|
||||
pub column_cache: SmallCache<Option<ColumnSample<'a>>>,
|
||||
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<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>> {
|
||||
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<i32>,
|
||||
z_cache: Option<&ZCache>,
|
||||
only_structures: bool,
|
||||
index: IndexRef<'a>,
|
||||
) -> Option<Block> {
|
||||
let BlockGen {
|
||||
column_cache,
|
||||
column_gen,
|
||||
} = self;
|
||||
pub fn get_with_z_cache(&mut self, wpos: Vec3<i32>, z_cache: Option<&ZCache>) -> Option<Block> {
|
||||
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<i32>,
|
||||
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<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,
|
||||
)
|
||||
})
|
||||
},
|
||||
}
|
||||
(min, max)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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");
|
||||
}
|
@ -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<i32>,
|
||||
pub(crate) column_grid: &'a Grid<Option<ZCache<'a>>>,
|
||||
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<i32> {
|
||||
self.wpos
|
||||
}
|
||||
pub fn wpos(&self) -> Vec2<i32> { self.wpos }
|
||||
|
||||
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
|
||||
.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<i32>,
|
||||
pub(crate) info: CanvasInfo<'a>,
|
||||
pub(crate) chunk: &'a mut TerrainChunk,
|
||||
}
|
||||
|
||||
impl<'a> Canvas<'a> {
|
||||
pub fn wpos(&self) -> Vec2<i32> {
|
||||
self.wpos
|
||||
}
|
||||
|
||||
pub fn area(&self) -> Aabr<i32> {
|
||||
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<i32>) -> Option<Block> {
|
||||
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) {
|
||||
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 }
|
||||
}
|
||||
|
@ -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<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> {
|
||||
@ -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<f32>,
|
||||
pub tree_density: f32,
|
||||
pub forest_kind: ForestKind,
|
||||
pub close_structures: [Option<StructureData>; 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<i32>,
|
||||
pub seed: u32,
|
||||
pub meta: Option<StructureMeta>,
|
||||
}
|
||||
|
@ -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<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
|
||||
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<u8>, 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<i32>,
|
||||
mut get_column: impl FnMut(Vec2<i32>) -> Option<&'a ColumnSample<'a>>,
|
||||
vol: &mut (impl BaseVol<Vox = Block> + 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::<SpriteKind>::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::<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)]
|
||||
pub fn apply_caves_supplement<'a>(
|
||||
|
@ -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<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,
|
||||
) {
|
||||
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)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
127
world/src/layer/tree.rs
Normal file
127
world/src/layer/tree.rs
Normal 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));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
@ -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| {
|
||||
|
@ -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<i32>) -> Option<(f32, Vec2<f32>, Cave, Vec2<f32>)> {
|
||||
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)]
|
||||
|
Loading…
Reference in New Issue
Block a user