mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge branch 'zesterer/worldgen-canvas' into 'master'
*The* world generation cleanup (part 1) See merge request veloren/veloren!1490
This commit is contained in:
commit
6d768e9685
@ -62,6 +62,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Allowed collecting nearby blocks without aiming at them
|
||||
- Made voxygen wait until singleplayer server is initialized before attempting to connect, removing the chance for it to give up on connecting if the server takes a while to start
|
||||
- Log where userdata folder is located
|
||||
- Switched to a Whittaker map for better tree spawning patterns
|
||||
- Switched to procedural snow cover on trees
|
||||
- Significantly improved terrain generation performance
|
||||
|
||||
### Removed
|
||||
|
||||
|
BIN
assets/voxygen/element/map/dungeon.png
Normal file
BIN
assets/voxygen/element/map/dungeon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 932 B |
BIN
assets/voxygen/element/map/town.png
Normal file
BIN
assets/voxygen/element/map/town.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
@ -1264,7 +1264,7 @@
|
||||
),
|
||||
Consumable("SunflowerTea"): Png(
|
||||
"element.icons.item_sunflower_tea",
|
||||
),
|
||||
),
|
||||
// Throwables
|
||||
Throwable(Bomb): VoxTrans(
|
||||
"voxel.object.bomb",
|
||||
@ -1344,7 +1344,7 @@
|
||||
"voxel.object.potion_empty",
|
||||
(0.0, 0.0, 0.0), (-50.0, 30.0, 20.0), 0.8,
|
||||
),
|
||||
// Gliders
|
||||
// Gliders
|
||||
Glider("Starter"): VoxTrans(
|
||||
"voxel.glider.glider_starter",
|
||||
(-2.0, 0.0, 0.0), (-50.0, 30.0, 20.0), 0.9,
|
||||
|
@ -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);
|
||||
|
@ -1,36 +0,0 @@
|
||||
#![enable(unwrap_newtypes)]
|
||||
|
||||
[
|
||||
(
|
||||
specifier: "world.tree.snow_pine.1",
|
||||
center: (15, 15, 14)
|
||||
),
|
||||
(
|
||||
specifier: "world.tree.snow_pine.2",
|
||||
center: (15, 15, 14)
|
||||
),
|
||||
(
|
||||
specifier: "world.tree.snow_pine.3",
|
||||
center: (17, 15, 12)
|
||||
),
|
||||
(
|
||||
specifier: "world.tree.snow_pine.4",
|
||||
center: (10, 8, 12)
|
||||
),
|
||||
(
|
||||
specifier: "world.tree.snow_pine.5",
|
||||
center: (12, 12, 12)
|
||||
),
|
||||
(
|
||||
specifier: "world.tree.snow_pine.6",
|
||||
center: (11, 10, 12)
|
||||
),
|
||||
(
|
||||
specifier: "world.tree.snow_pine.7",
|
||||
center: (16, 15, 12)
|
||||
),
|
||||
(
|
||||
specifier: "world.tree.snow_pine.8",
|
||||
center: (12, 10, 12)
|
||||
),
|
||||
]
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -34,6 +34,7 @@ make_case_elim!(
|
||||
WeakRock = 0x11, // Explodable
|
||||
// 0x12 <= x < 0x20 is reserved for future rocks
|
||||
Grass = 0x20, // Note: *not* the same as grass sprites
|
||||
Snow = 0x21,
|
||||
// 0x21 <= x < 0x30 is reserved for future grasses
|
||||
Earth = 0x30,
|
||||
Sand = 0x31,
|
||||
|
@ -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)
|
||||
|
@ -4,6 +4,6 @@ pub enum ForestKind {
|
||||
Savannah,
|
||||
Oak,
|
||||
Pine,
|
||||
SnowPine,
|
||||
Birch,
|
||||
Mangrove,
|
||||
}
|
||||
|
@ -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,131 @@ 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);
|
||||
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);
|
||||
|
||||
block
|
||||
})
|
||||
.or_else(|| {
|
||||
// Water
|
||||
if (wposf.z as f32) < water_height {
|
||||
// Ocean
|
||||
Some(water)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
96
world/src/canvas.rs
Normal file
96
world/src/canvas.rs
Normal file
@ -0,0 +1,96 @@
|
||||
use crate::{
|
||||
block::ZCache,
|
||||
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 area(&self) -> Aabr<i32> {
|
||||
Rect::from((
|
||||
self.wpos(),
|
||||
Extent2::from(TerrainChunkSize::RECT_SIZE.map(|e| e as i32)),
|
||||
))
|
||||
.into()
|
||||
}
|
||||
|
||||
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)
|
||||
.flatten()
|
||||
.map(|zc| &zc.sample)
|
||||
}
|
||||
|
||||
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) info: CanvasInfo<'a>,
|
||||
pub(crate) chunk: &'a mut TerrainChunk,
|
||||
}
|
||||
|
||||
impl<'a> Canvas<'a> {
|
||||
/// 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()
|
||||
}
|
||||
|
||||
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 }
|
||||
}
|
@ -646,16 +646,16 @@ fn walk_in_dir(sim: &WorldSim, a: Vec2<i32>, dir: Vec2<i32>) -> Option<f32> {
|
||||
let a_chunk = sim.get(a)?;
|
||||
let b_chunk = sim.get(a + dir)?;
|
||||
|
||||
let hill_cost = ((b_chunk.alt - a_chunk.alt).abs() / 2.5).powf(2.0);
|
||||
let hill_cost = ((b_chunk.alt - a_chunk.alt).abs() / 5.0).powf(2.0);
|
||||
let water_cost = if b_chunk.river.near_water() {
|
||||
50.0
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
} + (b_chunk.water_alt - b_chunk.alt + 8.0).clamped(0.0, 8.0) * 3.0; // Try not to path swamps / tidal areas
|
||||
let wild_cost = if b_chunk.path.0.is_way() {
|
||||
0.0 // Traversing existing paths has no additional cost!
|
||||
} else {
|
||||
2.0
|
||||
3.0 // + (1.0 - b_chunk.tree_density) * 20.0 // Prefer going through forests, for aesthetics
|
||||
};
|
||||
Some(1.0 + hill_cost + water_cost + wild_cost)
|
||||
} else {
|
||||
|
@ -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> {
|
||||
@ -456,12 +405,6 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
|
||||
river_overlap_distance_product / overlap_count
|
||||
} as f32;
|
||||
|
||||
let cliff_hill = (sim
|
||||
.gen_ctx
|
||||
.small_nz
|
||||
.get((wposf_turb.div(128.0)).into_array()) as f32)
|
||||
.mul(4.0);
|
||||
|
||||
let riverless_alt_delta = (sim.gen_ctx.small_nz.get(
|
||||
(wposf_turb.div(200.0 * (32.0 / TerrainChunkSize::RECT_SIZE.x as f64))).into_array(),
|
||||
) as f32)
|
||||
@ -491,9 +434,6 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
|
||||
})
|
||||
.unwrap_or(CONFIG.sea_level);
|
||||
|
||||
let is_cliffs = sim_chunk.is_cliffs;
|
||||
let near_cliffs = sim_chunk.near_cliffs;
|
||||
|
||||
let river_gouge = 0.5;
|
||||
let (_in_water, water_dist, alt_, water_level, riverless_alt, warp_factor) = if let Some(
|
||||
(max_border_river_pos, river_chunk, max_border_river, max_border_river_dist),
|
||||
@ -508,43 +448,44 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
|
||||
max_border_river
|
||||
.river_kind
|
||||
.and_then(|river_kind| {
|
||||
if let RiverKind::River { cross_section } = river_kind {
|
||||
if max_border_river_dist.map(|(_, dist, _, _)| dist)
|
||||
!= Some(Vec2::zero())
|
||||
{
|
||||
return None;
|
||||
}
|
||||
let (
|
||||
_,
|
||||
_,
|
||||
river_width,
|
||||
(river_t, (river_pos, _), downhill_river_chunk),
|
||||
) = max_border_river_dist.unwrap();
|
||||
let river_alt = Lerp::lerp(
|
||||
river_chunk.alt.max(river_chunk.water_alt),
|
||||
downhill_river_chunk.alt.max(downhill_river_chunk.water_alt),
|
||||
river_t as f32,
|
||||
);
|
||||
let new_alt = river_alt - river_gouge;
|
||||
let river_dist = wposf.distance(river_pos);
|
||||
let river_height_factor = river_dist / (river_width * 0.5);
|
||||
match river_kind {
|
||||
RiverKind::River { cross_section } => {
|
||||
if max_border_river_dist.map(|(_, dist, _, _)| dist)
|
||||
!= Some(Vec2::zero())
|
||||
{
|
||||
return None;
|
||||
}
|
||||
let (
|
||||
_,
|
||||
_,
|
||||
river_width,
|
||||
(river_t, (river_pos, _), downhill_river_chunk),
|
||||
) = max_border_river_dist.unwrap();
|
||||
let river_alt = Lerp::lerp(
|
||||
river_chunk.alt.max(river_chunk.water_alt),
|
||||
downhill_river_chunk.alt.max(downhill_river_chunk.water_alt),
|
||||
river_t as f32,
|
||||
);
|
||||
let new_alt = river_alt - river_gouge;
|
||||
let river_dist = wposf.distance(river_pos);
|
||||
let river_height_factor = river_dist / (river_width * 0.5);
|
||||
|
||||
let valley_alt = Lerp::lerp(
|
||||
new_alt - cross_section.y.max(1.0),
|
||||
new_alt - 1.0,
|
||||
(river_height_factor * river_height_factor) as f32,
|
||||
);
|
||||
let valley_alt = Lerp::lerp(
|
||||
new_alt - cross_section.y.max(1.0),
|
||||
new_alt - 1.0,
|
||||
(river_height_factor * river_height_factor) as f32,
|
||||
);
|
||||
|
||||
Some((
|
||||
true,
|
||||
Some((river_dist - river_width * 0.5) as f32),
|
||||
valley_alt,
|
||||
new_alt,
|
||||
river_alt,
|
||||
0.0,
|
||||
))
|
||||
} else {
|
||||
None
|
||||
Some((
|
||||
true,
|
||||
Some((river_dist - river_width * 0.5) as f32),
|
||||
valley_alt,
|
||||
new_alt,
|
||||
alt, //river_alt + cross_section.y.max(1.0),
|
||||
0.0,
|
||||
))
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
@ -693,7 +634,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
|
||||
} else {
|
||||
return (
|
||||
true,
|
||||
None,
|
||||
Some(lake_dist as f32),
|
||||
alt_for_river,
|
||||
if in_bounds_ {
|
||||
downhill_water_alt.max(lake_water_alt)
|
||||
@ -726,7 +667,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
|
||||
Some((river_dist - river_width * 0.5) as f32),
|
||||
alt_for_river,
|
||||
downhill_water_alt,
|
||||
alt_for_river,
|
||||
alt, //alt_for_river,
|
||||
river_scale_factor as f32,
|
||||
)
|
||||
},
|
||||
@ -737,7 +678,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
|
||||
None,
|
||||
alt_for_river,
|
||||
downhill_water_alt,
|
||||
alt_for_river,
|
||||
alt, //alt_for_river,
|
||||
river_scale_factor as f32,
|
||||
))
|
||||
});
|
||||
@ -755,7 +696,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
|
||||
None,
|
||||
alt_for_river,
|
||||
downhill_water_alt,
|
||||
alt_for_river,
|
||||
alt, //alt_for_river,
|
||||
1.0,
|
||||
)
|
||||
};
|
||||
@ -780,6 +721,26 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
|
||||
.max(0.0)
|
||||
.mul(8.0);
|
||||
|
||||
// Columns near water have a more stable temperature and so get pushed towards
|
||||
// the average (0)
|
||||
let temp = Lerp::lerp(
|
||||
Lerp::lerp(temp, 0.0, 0.1),
|
||||
temp,
|
||||
water_dist
|
||||
.map(|water_dist| water_dist / 20.0)
|
||||
.unwrap_or(1.0)
|
||||
.clamped(0.0, 1.0),
|
||||
);
|
||||
// Columns near water get a humidity boost
|
||||
let humidity = Lerp::lerp(
|
||||
Lerp::lerp(humidity, 1.0, 0.25),
|
||||
humidity,
|
||||
water_dist
|
||||
.map(|water_dist| water_dist / 20.0)
|
||||
.unwrap_or(1.0)
|
||||
.clamped(0.0, 1.0),
|
||||
);
|
||||
|
||||
let wposf3d = Vec3::new(wposf.x, wposf.y, alt as f64);
|
||||
|
||||
let marble_small = (sim.gen_ctx.hill_nz.get((wposf3d.div(3.0)).into_array()) as f32)
|
||||
@ -918,7 +879,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
|
||||
humidity
|
||||
.sub(CONFIG.desert_hum)
|
||||
.div(CONFIG.forest_hum.sub(CONFIG.desert_hum))
|
||||
.mul(1.0),
|
||||
.mul(1.25),
|
||||
);
|
||||
// From forest to jungle humidity, we go from snow to dark grass to grass to
|
||||
// tropics to sand depending on temperature.
|
||||
@ -986,15 +947,17 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
|
||||
.max(-humidity.sub(CONFIG.desert_hum))
|
||||
.mul(16.0)
|
||||
.add((marble_small - 0.5) * 0.5);
|
||||
let (alt, ground, sub_surface_color) = if snow_cover <= 0.5 && alt > water_level {
|
||||
let (alt, ground, sub_surface_color, snow_cover) = if snow_cover <= 0.5 && alt > water_level
|
||||
{
|
||||
// Allow snow cover.
|
||||
(
|
||||
alt + 1.0 - snow_cover.max(0.0),
|
||||
Rgb::lerp(snow, ground, snow_cover),
|
||||
Lerp::lerp(sub_surface_color, ground, alt.sub(basement).mul(0.15)),
|
||||
true,
|
||||
)
|
||||
} else {
|
||||
(alt, ground, sub_surface_color)
|
||||
(alt, ground, sub_surface_color, false)
|
||||
};
|
||||
|
||||
// Make river banks not have grass
|
||||
@ -1004,7 +967,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
|
||||
|
||||
let near_ocean = max_river.and_then(|(_, _, river_data, _)| {
|
||||
if (river_data.is_lake() || river_data.river_kind == Some(RiverKind::Ocean))
|
||||
&& ((alt <= water_level.max(CONFIG.sea_level + 5.0) && !is_cliffs) || !near_cliffs)
|
||||
&& alt <= water_level.max(CONFIG.sea_level + 5.0)
|
||||
{
|
||||
Some(water_level)
|
||||
} else {
|
||||
@ -1053,14 +1016,9 @@ 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,
|
||||
is_cliffs,
|
||||
near_cliffs,
|
||||
cliff_hill,
|
||||
close_cliffs: sim.gen_ctx.cliff_gen.get(wpos),
|
||||
temp,
|
||||
humidity,
|
||||
spawn_rate,
|
||||
@ -1068,6 +1026,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
|
||||
water_dist,
|
||||
path,
|
||||
cave,
|
||||
snow_cover,
|
||||
|
||||
chunk: sim_chunk,
|
||||
})
|
||||
@ -1086,14 +1045,9 @@ 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,
|
||||
pub is_cliffs: bool,
|
||||
pub near_cliffs: bool,
|
||||
pub cliff_hill: f32,
|
||||
pub close_cliffs: [(Vec2<i32>, u32); 9],
|
||||
pub temp: f32,
|
||||
pub humidity: f32,
|
||||
pub spawn_rate: f32,
|
||||
@ -1101,13 +1055,7 @@ pub struct ColumnSample<'a> {
|
||||
pub water_dist: Option<f32>,
|
||||
pub path: Option<(f32, Vec2<f32>, Path, Vec2<f32>)>,
|
||||
pub cave: Option<(f32, Vec2<f32>, Cave, Vec2<f32>)>,
|
||||
pub snow_cover: bool,
|
||||
|
||||
pub chunk: &'a SimChunk,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct StructureData {
|
||||
pub pos: Vec2<i32>,
|
||||
pub seed: u32,
|
||||
pub meta: Option<StructureMeta>,
|
||||
}
|
||||
|
@ -59,7 +59,7 @@ pub const CONFIG: Config = Config {
|
||||
desert_temp: 0.8,
|
||||
desert_hum: 0.15,
|
||||
forest_hum: 0.5,
|
||||
jungle_hum: 0.85,
|
||||
jungle_hum: 0.75,
|
||||
rainfall_chunk_rate: 1.0 / (512.0 * 32.0 * 32.0),
|
||||
river_roughness: 0.06125,
|
||||
river_max_width: 2.0,
|
||||
|
@ -1,11 +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, IndexRef,
|
||||
};
|
||||
use common::{
|
||||
assets::Asset,
|
||||
@ -32,180 +33,162 @@ pub struct Colors {
|
||||
|
||||
const EMPTY_AIR: Block = Block::air(SpriteKind::Empty);
|
||||
|
||||
pub fn apply_paths_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_paths_to(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;
|
||||
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) = get_column(offs) {
|
||||
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 = get_column(col_pos.map(|e| e.floor() as i32) + Vec2::new(0, 0));
|
||||
let col10 = get_column(col_pos.map(|e| e.floor() as i32) + Vec2::new(1, 0));
|
||||
let col01 = get_column(col_pos.map(|e| e.floor() as i32) + Vec2::new(0, 1));
|
||||
let col11 = get_column(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 _ = vol.set(
|
||||
Vec3::new(offs.x, offs.y, surface_z + z),
|
||||
if bridge_offset >= 2.0 && path_dist >= 3.0 || z < inset - 1 {
|
||||
Block::new(
|
||||
BlockKind::Rock,
|
||||
noisy_color(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(offs.x, offs.y, surface_z + z);
|
||||
if vol.get(pos).unwrap().kind() != BlockKind::Water {
|
||||
let _ = vol.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(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(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)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
145
world/src/layer/tree.rs
Normal file
145
world/src/layer/tree.rs
Normal file
@ -0,0 +1,145 @@
|
||||
use crate::{
|
||||
all::ForestKind,
|
||||
block::block_from_structure,
|
||||
column::ColumnGen,
|
||||
util::{RandomPerm, Sampler, UnitChooser},
|
||||
Canvas, CONFIG,
|
||||
};
|
||||
use common::{
|
||||
terrain::{structure::Structure, Block, BlockKind},
|
||||
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 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(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::Oak if QUIRKY_RAND.get(seed) % 16 == 7 => &OAK_STUMPS,
|
||||
ForestKind::Oak if QUIRKY_RAND.get(seed) % 19 == 7 => &FRUIT_TREES,
|
||||
ForestKind::Palm => &PALMS,
|
||||
ForestKind::Savannah => &ACACIAS,
|
||||
ForestKind::Oak => &OAKS,
|
||||
ForestKind::Pine => &PINES,
|
||||
ForestKind::Birch => &BIRCHES,
|
||||
ForestKind::Mangrove => &MANGROVE_TREES,
|
||||
}
|
||||
};
|
||||
Arc::clone(
|
||||
&models[(MODEL_RAND.get(seed.wrapping_mul(17)) / 13) as usize
|
||||
% models.len()],
|
||||
)
|
||||
},
|
||||
seed,
|
||||
units: UNIT_CHOOSER.get(seed),
|
||||
})
|
||||
}) {
|
||||
tree
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let bounds = tree.model.get_bounds();
|
||||
let mut is_top = true;
|
||||
let mut is_leaf_top = true;
|
||||
for z in (bounds.min.z..bounds.max.z).rev() {
|
||||
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(),
|
||||
if let Some(block) = tree.model.get(model_pos).ok().copied() {
|
||||
block
|
||||
} else {
|
||||
// If we hit an inaccessible block, we're probably outside the model bounds.
|
||||
// Skip this column.
|
||||
break;
|
||||
},
|
||||
wpos,
|
||||
tree.pos.xy(),
|
||||
tree.seed,
|
||||
col,
|
||||
Block::air,
|
||||
)
|
||||
.map(|block| {
|
||||
// Add a snow covering to the block above under certain circumstances
|
||||
if col.snow_cover
|
||||
&& ((block.kind() == BlockKind::Leaves && is_leaf_top)
|
||||
|| (is_top && block.is_filled()))
|
||||
{
|
||||
canvas.set(
|
||||
wpos + Vec3::unit_z(),
|
||||
Block::new(BlockKind::Snow, Rgb::new(210, 210, 255)),
|
||||
);
|
||||
}
|
||||
canvas.set(wpos, block);
|
||||
is_leaf_top = false;
|
||||
is_top = false;
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
is_leaf_top = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
@ -8,11 +8,13 @@
|
||||
const_generics,
|
||||
const_panic,
|
||||
label_break_value,
|
||||
or_patterns
|
||||
or_patterns,
|
||||
array_value_iter
|
||||
)]
|
||||
|
||||
mod all;
|
||||
mod block;
|
||||
pub mod canvas;
|
||||
pub mod civ;
|
||||
mod column;
|
||||
pub mod config;
|
||||
@ -25,7 +27,10 @@ pub mod site;
|
||||
pub mod util;
|
||||
|
||||
// Reexports
|
||||
pub use crate::config::CONFIG;
|
||||
pub use crate::{
|
||||
canvas::{Canvas, CanvasInfo},
|
||||
config::CONFIG,
|
||||
};
|
||||
pub use block::BlockGen;
|
||||
pub use column::ColumnSample;
|
||||
pub use index::{IndexOwned, IndexRef};
|
||||
@ -126,7 +131,6 @@ impl World {
|
||||
);
|
||||
let water = Block::new(BlockKind::Water, Rgb::zero());
|
||||
|
||||
let _chunk_size2d = TerrainChunkSize::RECT_SIZE;
|
||||
let (base_z, sim_chunk) = match self
|
||||
.sim
|
||||
/*.get_interpolated(
|
||||
@ -168,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);
|
||||
@ -178,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);
|
||||
}
|
||||
});
|
||||
@ -201,9 +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);
|
||||
layer::apply_paths_to(chunk_wpos2d, sample_get, &mut chunk, index);
|
||||
let mut canvas = Canvas {
|
||||
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_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| {
|
||||
|
@ -28,7 +28,10 @@ use crate::{
|
||||
civ::Place,
|
||||
column::ColumnGen,
|
||||
site::Site,
|
||||
util::{seed_expan, FastNoise, RandomField, Sampler, StructureGen2d, LOCALITY, NEIGHBORS},
|
||||
util::{
|
||||
seed_expan, FastNoise, RandomField, RandomPerm, Sampler, StructureGen2d, LOCALITY,
|
||||
NEIGHBORS,
|
||||
},
|
||||
IndexRef, CONFIG,
|
||||
};
|
||||
use common::{
|
||||
@ -103,7 +106,6 @@ pub(crate) struct GenCtx {
|
||||
// Small amounts of noise for simulating rough terrain.
|
||||
pub small_nz: BasicMulti,
|
||||
pub rock_nz: HybridMulti,
|
||||
pub cliff_nz: HybridMulti,
|
||||
pub warp_nz: FastNoise,
|
||||
pub tree_nz: BasicMulti,
|
||||
|
||||
@ -112,7 +114,6 @@ pub(crate) struct GenCtx {
|
||||
|
||||
pub structure_gen: StructureGen2d,
|
||||
pub region_gen: StructureGen2d,
|
||||
pub cliff_gen: StructureGen2d,
|
||||
|
||||
pub fast_turb_x_nz: FastNoise,
|
||||
pub fast_turb_y_nz: FastNoise,
|
||||
@ -503,7 +504,6 @@ impl WorldSim {
|
||||
|
||||
small_nz: BasicMulti::new().set_octaves(2).set_seed(rng.gen()),
|
||||
rock_nz: HybridMulti::new().set_persistence(0.3).set_seed(rng.gen()),
|
||||
cliff_nz: HybridMulti::new().set_persistence(0.3).set_seed(rng.gen()),
|
||||
warp_nz: FastNoise::new(rng.gen()),
|
||||
tree_nz: BasicMulti::new()
|
||||
.set_octaves(12)
|
||||
@ -514,7 +514,6 @@ impl WorldSim {
|
||||
|
||||
structure_gen: StructureGen2d::new(rng.gen(), 32, 16),
|
||||
region_gen: StructureGen2d::new(rng.gen(), 400, 96),
|
||||
cliff_gen: StructureGen2d::new(rng.gen(), 80, 56),
|
||||
humid_nz: Billow::new()
|
||||
.set_octaves(9)
|
||||
.set_persistence(0.4)
|
||||
@ -1431,25 +1430,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 +1982,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)]
|
||||
@ -2010,8 +2003,6 @@ pub struct SimChunk {
|
||||
pub temp: f32,
|
||||
pub humidity: f32,
|
||||
pub rockiness: f32,
|
||||
pub is_cliffs: bool,
|
||||
pub near_cliffs: bool,
|
||||
pub tree_density: f32,
|
||||
pub forest_kind: ForestKind,
|
||||
pub spawn_rate: f32,
|
||||
@ -2068,11 +2059,6 @@ impl SimChunk {
|
||||
// Even less granular--if this matters we can make the sign affect the quantity slightly.
|
||||
let abs_lat_uniform = latitude_uniform.abs(); */
|
||||
|
||||
// Take the weighted average of our randomly generated base humidity, and the
|
||||
// calculated water flux over this point in order to compute humidity.
|
||||
const HUMID_WEIGHTS: [f32; 2] = [2.0, 1.0];
|
||||
let humidity = cdf_irwin_hall(&HUMID_WEIGHTS, [humid_uniform, flux_uniform]);
|
||||
|
||||
// We also correlate temperature negatively with altitude and absolute latitude,
|
||||
// using different weighting than we use for humidity.
|
||||
const TEMP_WEIGHTS: [f32; 2] = [/* 1.5, */ 1.0, 2.0];
|
||||
@ -2087,6 +2073,18 @@ impl SimChunk {
|
||||
.sub(0.5)
|
||||
.mul(2.0);
|
||||
|
||||
// Take the weighted average of our randomly generated base humidity, and the
|
||||
// calculated water flux over this point in order to compute humidity.
|
||||
const HUMID_WEIGHTS: [f32; 3] = [1.0, 1.0, 0.75];
|
||||
let humidity = cdf_irwin_hall(&HUMID_WEIGHTS, [humid_uniform, flux_uniform, 1.0]);
|
||||
// Moisture evaporates more in hot places
|
||||
let humidity = humidity
|
||||
* (1.0
|
||||
- (temp - CONFIG.tropical_temp)
|
||||
.max(0.0)
|
||||
.div(1.0 - CONFIG.tropical_temp))
|
||||
.max(0.0);
|
||||
|
||||
let mut alt = CONFIG.sea_level.add(alt_pre);
|
||||
let basement = CONFIG.sea_level.add(basement_pre);
|
||||
let water_alt = CONFIG.sea_level.add(water_alt_pre);
|
||||
@ -2101,10 +2099,6 @@ impl SimChunk {
|
||||
)
|
||||
};
|
||||
|
||||
//let cliff = gen_ctx.cliff_nz.get((wposf.div(2048.0)).into_array()) as f32 +
|
||||
// chaos * 0.2;
|
||||
let cliff = 0.0; // Disable cliffs
|
||||
|
||||
// Logistic regression. Make sure x ∈ (0, 1).
|
||||
let logit = |x: f64| x.ln() - x.neg().ln_1p();
|
||||
// 0.5 + 0.5 * tanh(ln(1 / (1 - 0.1) - 1) / (2 * (sqrt(3)/pi)))
|
||||
@ -2154,7 +2148,6 @@ impl SimChunk {
|
||||
.mul(1.5)
|
||||
.add(1.0)
|
||||
.mul(0.5)
|
||||
.mul(1.2 - chaos as f64 * 0.95)
|
||||
.add(0.05)
|
||||
.max(0.0)
|
||||
.min(1.0);
|
||||
@ -2165,13 +2158,31 @@ impl SimChunk {
|
||||
1.0
|
||||
} else {
|
||||
// Weighted logit sum.
|
||||
logistic_cdf(logit(humidity as f64) + 0.5 * logit(tree_density))
|
||||
logistic_cdf(logit(tree_density))
|
||||
}
|
||||
// rescale to (-0.95, 0.95)
|
||||
.sub(0.5)
|
||||
.mul(0.95)
|
||||
.add(0.5)
|
||||
} as f32;
|
||||
const MIN_TREE_HUM: f32 = 0.15;
|
||||
// Tree density increases exponentially with humidity...
|
||||
let tree_density = (tree_density * (humidity - MIN_TREE_HUM).max(0.0).mul(1.0 + MIN_TREE_HUM) / temp.max(0.75))
|
||||
// ...but is ultimately limited by available sunlight (and our tree generation system)
|
||||
.min(1.0);
|
||||
|
||||
// Sand dunes (formed over a short period of time)
|
||||
let alt = alt
|
||||
+ if river.near_water() {
|
||||
0.0
|
||||
} else {
|
||||
let warp = Vec2::new(
|
||||
gen_ctx.turb_x_nz.get(wposf.div(256.0).into_array()) as f32,
|
||||
gen_ctx.turb_y_nz.get(wposf.div(256.0).into_array()) as f32,
|
||||
) * 192.0;
|
||||
let dune_nz = (wposf.map(|e| e as f32) + warp).sum().div(100.0).sin() * 0.5 + 0.5;
|
||||
let dune_scale = 16.0;
|
||||
dune_nz * dune_scale * (temp - 0.75).clamped(0.0, 0.25) * 4.0
|
||||
};
|
||||
|
||||
Self {
|
||||
chaos,
|
||||
@ -2191,67 +2202,71 @@ impl SimChunk {
|
||||
} else {
|
||||
0.0
|
||||
},
|
||||
is_cliffs: cliff > 0.5 && !is_underwater,
|
||||
near_cliffs: cliff > 0.2,
|
||||
tree_density,
|
||||
forest_kind: if temp > CONFIG.temperate_temp {
|
||||
if temp > CONFIG.desert_temp {
|
||||
if humidity > CONFIG.jungle_hum {
|
||||
// Forests in desert temperatures with extremely high humidity
|
||||
// should probably be different from palm trees, but we use them
|
||||
// for now.
|
||||
ForestKind::Palm
|
||||
} else if humidity > CONFIG.forest_hum {
|
||||
ForestKind::Palm
|
||||
} else if humidity > CONFIG.desert_hum {
|
||||
// Low but not desert humidity, so we should really have some other
|
||||
// terrain...
|
||||
ForestKind::Savannah
|
||||
} else {
|
||||
ForestKind::Savannah
|
||||
}
|
||||
} else if temp > CONFIG.tropical_temp {
|
||||
if humidity > CONFIG.jungle_hum {
|
||||
if tree_density > 0.0 {
|
||||
// println!("Mangrove: {:?}", wposf);
|
||||
}
|
||||
ForestKind::Mangrove
|
||||
} else if humidity > CONFIG.forest_hum {
|
||||
// NOTE: Probably the wrong kind of tree for this climate.
|
||||
ForestKind::Oak
|
||||
} else if humidity > CONFIG.desert_hum {
|
||||
// Low but not desert... need something besides savannah.
|
||||
ForestKind::Savannah
|
||||
} else {
|
||||
ForestKind::Savannah
|
||||
}
|
||||
} else if humidity > CONFIG.jungle_hum {
|
||||
// Temperate climate with jungle humidity...
|
||||
// https://en.wikipedia.org/wiki/Humid_subtropical_climates are often
|
||||
// densely wooded and full of water. Semitropical rainforests, basically.
|
||||
// For now we just treat them like other rainforests.
|
||||
ForestKind::Oak
|
||||
} else if humidity > CONFIG.forest_hum {
|
||||
// Moderate climate, moderate humidity.
|
||||
ForestKind::Oak
|
||||
} else if humidity > CONFIG.desert_hum {
|
||||
// With moderate temperature and low humidity, we should probably see
|
||||
// something different from savannah, but oh well...
|
||||
ForestKind::Savannah
|
||||
} else {
|
||||
ForestKind::Savannah
|
||||
}
|
||||
} else {
|
||||
// For now we don't take humidity into account for cold climates (but we really
|
||||
// should!) except that we make sure we only have snow pines when there is snow.
|
||||
if temp <= CONFIG.snow_temp {
|
||||
ForestKind::SnowPine
|
||||
} else if humidity > CONFIG.desert_hum {
|
||||
ForestKind::Pine
|
||||
} else {
|
||||
// Should really have something like tundra.
|
||||
ForestKind::Pine
|
||||
}
|
||||
forest_kind: {
|
||||
// Whittaker diagram
|
||||
let candidates = [
|
||||
// A smaller prevalence means that the range of values this tree appears in
|
||||
// will shrink compared to neighbouring trees in the
|
||||
// topology of the Whittaker diagram.
|
||||
// Humidity, temperature, near_water, each with prevalence
|
||||
(
|
||||
ForestKind::Palm,
|
||||
(CONFIG.desert_hum, 1.5),
|
||||
(CONFIG.tropical_temp, 1.5),
|
||||
(1.0, 2.0),
|
||||
),
|
||||
(
|
||||
ForestKind::Savannah,
|
||||
(CONFIG.desert_hum, 2.0),
|
||||
(CONFIG.tropical_temp, 1.5),
|
||||
(0.0, 1.0),
|
||||
),
|
||||
(
|
||||
ForestKind::Oak,
|
||||
(CONFIG.forest_hum, 1.5),
|
||||
(0.0, 1.5),
|
||||
(0.0, 1.0),
|
||||
),
|
||||
(
|
||||
ForestKind::Mangrove,
|
||||
(CONFIG.jungle_hum, 0.5),
|
||||
(CONFIG.tropical_temp, 0.5),
|
||||
(0.0, 1.0),
|
||||
),
|
||||
(
|
||||
ForestKind::Pine,
|
||||
(CONFIG.forest_hum, 1.25),
|
||||
(CONFIG.snow_temp, 2.5),
|
||||
(0.0, 1.0),
|
||||
),
|
||||
(
|
||||
ForestKind::Birch,
|
||||
(CONFIG.desert_hum, 1.5),
|
||||
(CONFIG.temperate_temp, 1.5),
|
||||
(0.0, 1.0),
|
||||
),
|
||||
];
|
||||
|
||||
candidates
|
||||
.iter()
|
||||
.enumerate()
|
||||
.min_by_key(|(i, (_, (h, h_prev), (t, t_prev), (w, w_prev)))| {
|
||||
let rand = RandomPerm::new(*i as u32 * 1000);
|
||||
let noise =
|
||||
Vec3::iota().map(|e| (rand.get(e) & 0xFF) as f32 / 255.0 - 0.5) * 2.0;
|
||||
(Vec3::new(
|
||||
(*h - humidity) / *h_prev,
|
||||
(*t - temp) / *t_prev,
|
||||
(*w - if river.near_water() { 1.0 } else { 0.0 }) / *w_prev,
|
||||
)
|
||||
.add(noise * 0.1)
|
||||
.map(|e| e * e)
|
||||
.sum()
|
||||
* 10000.0) as i32
|
||||
})
|
||||
.map(|(_, c)| c.0)
|
||||
.unwrap() // Can't fail
|
||||
},
|
||||
spawn_rate: 1.0,
|
||||
river,
|
||||
|
@ -93,7 +93,7 @@ pub fn center_of(p: [Vec2<f32>; 3]) -> Vec2<f32> {
|
||||
impl WorldSim {
|
||||
fn can_host_settlement(&self, pos: Vec2<i32>) -> bool {
|
||||
self.get(pos)
|
||||
.map(|chunk| !chunk.near_cliffs && !chunk.river.is_river() && !chunk.river.is_lake())
|
||||
.map(|chunk| !chunk.river.is_river() && !chunk.river.is_lake())
|
||||
.unwrap_or(false)
|
||||
&& self
|
||||
.get_gradient_approx(pos)
|
||||
|
Loading…
x
Reference in New Issue
Block a user