Experimental giant mother trees

This commit is contained in:
Joshua Barretto 2021-02-07 19:55:13 +00:00
parent ac5a60d260
commit 24c75f7cc3
11 changed files with 180 additions and 129 deletions

View File

@ -275,7 +275,7 @@ void main() {
vec3(0, 0, -2)
) + vec3(sin(lifetime), sin(lifetime + 0.7), sin(lifetime * 0.5)) * 2.0,
vec3(4),
vec4(vec3(0.2 + rand7 * 0.2, 0.2 + (0.5 + rand6 * 0.5) * 0.6, 0), 1),
vec4(vec3(0.2 + rand7 * 0.2, 0.2 + (0.5 + rand6 * 0.5) * 0.6, 0) * (0.25 + rand1 * 0.5), 1),
spin_in_axis(vec3(rand6, rand7, rand8), rand9 * 3 + lifetime * 5)
);
} else if (inst_mode == SNOW) {

View File

@ -196,8 +196,8 @@ impl Block {
#[inline]
pub fn get_max_sunlight(&self) -> Option<u8> {
match self.kind() {
BlockKind::Water => Some(2),
BlockKind::Leaves => Some(4),
BlockKind::Water => Some(3),
BlockKind::Leaves => Some(6),
_ if self.is_opaque() => Some(0),
_ => None,
}

View File

@ -69,25 +69,23 @@ fn calc_light<V: RectRasterableVol<Vox = Block> + ReadVol + Debug>(
if is_sunlight {
for x in 0..outer.size().w {
for y in 0..outer.size().h {
let z = outer.size().d - 1;
let is_air = vol_cached
.get(outer.min + Vec3::new(x, y, z))
.ok()
.map_or(false, |b| b.is_air());
light_map[lm_idx(x, y, z)] = if is_air {
if vol_cached
.get(outer.min + Vec3::new(x, y, z - 1))
.ok()
.map_or(false, |b| b.is_air())
let mut light = SUNLIGHT;
for z in (0..outer.size().d).rev() {
match vol_cached
.get(outer.min + Vec3::new(x, y, z))
.map_or(None, |b| b.get_max_sunlight())
{
light_map[lm_idx(x, y, z - 1)] = SUNLIGHT;
prop_que.push_back((x as u8, y as u8, z as u16));
None => {},
Some(0) => {
light_map[lm_idx(x, y, z)] = 0;
break;
},
Some(max_sunlight) => light = light.min(max_sunlight),
}
SUNLIGHT
} else {
OPAQUE
};
light_map[lm_idx(x, y, z)] = light;
prop_que.push_back((x as u8, y as u8, z as u16));
}
}
}
}
@ -129,7 +127,7 @@ fn calc_light<V: RectRasterableVol<Vox = Block> + ReadVol + Debug>(
let light = light_map[lm_idx(pos.x, pos.y, pos.z)];
// If ray propagate downwards at full strength
if is_sunlight && light == SUNLIGHT {
if is_sunlight && light == SUNLIGHT && false {
// Down is special cased and we know up is a ray
// Special cased ray propagation
let pos = Vec3::new(pos.x, pos.y, pos.z - 1);
@ -401,7 +399,9 @@ impl<'a, V: RectRasterableVol<Vox = Block> + ReadVol + Debug + 'static>
let greedy_size_cross = Vec3::new(greedy_size.x - 1, greedy_size.y - 1, greedy_size.z);
let draw_delta = Vec3::new(1, 1, z_start);
let get_light = |_: &mut (), pos: Vec3<i32>| light(pos + range.min);
let get_light = |_: &mut (), pos: Vec3<i32>| volume
.get(range.min + pos)
.map_or(1.0, |b| if b.is_opaque() { 0.0 } else { light(pos + range.min) });
let get_glow = |_: &mut (), pos: Vec3<i32>| glow(pos + range.min);
let get_color =
|_: &mut (), pos: Vec3<i32>| flat_get(pos).get_color().unwrap_or(Rgb::zero());

View File

@ -1015,7 +1015,7 @@ impl Renderer {
/// NOTE: Apparently Macs aren't the only machines that lie.
///
/// TODO: Find a way to let graphics cards that don't lie do better.
const MAX_TEXTURE_SIZE_MAX: u16 = 8192;
const MAX_TEXTURE_SIZE_MAX: u16 = 8192 << 1;
// NOTE: Many APIs for textures require coordinates to fit in u16, which is why
// we perform this conversion.
u16::try_from(factory.get_capabilities().max_texture_size)

View File

@ -14,6 +14,7 @@ common = { package = "veloren-common", path = "../common" }
common-net = { package = "veloren-common-net", path = "../common/net" }
bincode = "1.3.1"
bitvec = "0.21.0"
enum-iterator = "0.6"
fxhash = "0.2.1"
image = { version = "0.23.12", default-features = false, features = ["png"] }
itertools = "0.10"

View File

@ -1,4 +1,9 @@
#[derive(Copy, Clone, Debug)]
use std::ops::Range;
use enum_iterator::IntoEnumIterator;
use vek::*;
use crate::util::math::close;
#[derive(Copy, Clone, Debug, IntoEnumIterator)]
pub enum ForestKind {
Palm,
Acacia,
@ -9,3 +14,73 @@ pub enum ForestKind {
Mangrove,
Swamp,
}
pub struct Environment {
pub humid: f32,
pub temp: f32,
pub near_water: f32,
}
impl ForestKind {
pub fn humid_range(&self) -> Range<f32> {
match self {
ForestKind::Palm => 0.25..1.4,
ForestKind::Acacia => 0.1..0.6,
ForestKind::Baobab => 0.2..0.6,
ForestKind::Oak => 0.5..1.5,
ForestKind::Pine => 0.2..1.4,
ForestKind::Birch => 0.0..0.6,
ForestKind::Mangrove => 0.7..1.3,
ForestKind::Swamp => 0.5..1.1,
}
}
pub fn temp_range(&self) -> Range<f32> {
match self {
ForestKind::Palm => 0.4..1.6,
ForestKind::Acacia => 0.4..1.6,
ForestKind::Baobab => 0.4..0.9,
ForestKind::Oak => -0.35..0.5,
ForestKind::Pine => -1.8..-0.2,
ForestKind::Birch => -0.7..0.25,
ForestKind::Mangrove => 0.4..1.6,
ForestKind::Swamp => -0.6..0.8,
}
}
pub fn near_water_range(&self) -> Option<Range<f32>> {
match self {
ForestKind::Palm => Some(0.35..1.8),
ForestKind::Swamp => Some(0.5..1.8),
_ => None,
}
}
/// The relative rate at which this tree appears under ideal conditions
pub fn ideal_proclivity(&self) -> f32 {
match self {
ForestKind::Palm => 0.4,
ForestKind::Acacia => 0.6,
ForestKind::Baobab => 0.2,
ForestKind::Oak => 1.0,
ForestKind::Pine => 1.0,
ForestKind::Birch => 0.65,
ForestKind::Mangrove => 1.0,
ForestKind::Swamp => 1.0,
}
}
pub fn proclivity(&self, env: &Environment) -> f32 {
self.ideal_proclivity()
* close(env.humid, self.humid_range())
* close(env.temp, self.temp_range())
* self.near_water_range().map_or(1.0, |near_water_range| close(env.near_water, near_water_range))
}
}
pub struct TreeAttr {
pub pos: Vec2<i32>,
pub seed: u32,
pub scale: f32,
pub forest_kind: ForestKind,
}

View File

@ -982,6 +982,13 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
.map(|wd| Lerp::lerp(sub_surface_color, ground, (wd / 3.0).clamped(0.0, 1.0)))
.unwrap_or(ground);
// Ground under thick trees should be receive less sunlight and so often become dirt
let ground = Lerp::lerp(
ground,
sub_surface_color,
marble_mid * tree_density,
);
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)

View File

@ -1,5 +1,5 @@
use crate::{
all::ForestKind,
all::*,
block::block_from_structure,
column::ColumnGen,
util::{RandomPerm, Sampler, UnitChooser},
@ -60,9 +60,9 @@ pub fn apply_trees_to(canvas: &mut Canvas) {
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()))?;
for TreeAttr { pos, seed, scale, forest_kind } in trees {
let tree = if let Some(tree) = tree_cache.entry(pos).or_insert_with(|| {
let col = ColumnGen::new(info.land()).get((pos, info.index()))?;
let is_quirky = QUIRKY_RAND.chance(seed, 1.0 / 500.0);
@ -82,7 +82,7 @@ pub fn apply_trees_to(canvas: &mut Canvas) {
}
Some(Tree {
pos: Vec3::new(tree_wpos.x, tree_wpos.y, col.alt as i32),
pos: Vec3::new(pos.x, pos.y, col.alt as i32),
model: 'model: {
let models: AssetHandle<_> = if is_quirky {
if col.temp > CONFIG.desert_temp {
@ -91,7 +91,7 @@ pub fn apply_trees_to(canvas: &mut Canvas) {
*QUIRKY
}
} else {
match col.forest_kind {
match forest_kind {
ForestKind::Oak if QUIRKY_RAND.chance(seed + 1, 1.0 / 16.0) => {
*OAK_STUMPS
},
@ -105,7 +105,7 @@ pub fn apply_trees_to(canvas: &mut Canvas) {
ForestKind::Oak => {
break 'model TreeModel::Procedural(
ProceduralTree::generate(
TreeConfig::oak(&mut RandomPerm::new(seed)),
TreeConfig::oak(&mut RandomPerm::new(seed), scale),
&mut RandomPerm::new(seed),
),
StructureBlock::TemperateLeaves,
@ -115,7 +115,7 @@ pub fn apply_trees_to(canvas: &mut Canvas) {
ForestKind::Pine => {
break 'model TreeModel::Procedural(
ProceduralTree::generate(
TreeConfig::pine(&mut RandomPerm::new(seed)),
TreeConfig::pine(&mut RandomPerm::new(seed), scale),
&mut RandomPerm::new(seed),
),
StructureBlock::PineLeaves,
@ -230,7 +230,7 @@ pub struct TreeConfig {
/// Maximum number of branch layers (not including trunk).
pub max_depth: usize,
/// The number of branches that form from each branch.
pub splits: usize,
pub splits: Range<f32>,
/// The range of proportions along a branch at which a split into another
/// branch might occur. This value is clamped between 0 and 1, but a
/// wider range may bias the results towards branch ends.
@ -250,37 +250,39 @@ pub struct TreeConfig {
}
impl TreeConfig {
pub fn oak(rng: &mut impl Rng) -> Self {
let scale = Lerp::lerp(0.8, 1.5, rng.gen::<f32>().powi(4));
pub fn oak(rng: &mut impl Rng, scale: f32) -> Self {
let scale = scale * (1.0 + rng.gen::<f32>().powi(4));
let log_scale = 1.0 + scale.log2().max(0.0);
Self {
trunk_len: 12.0 * scale,
trunk_radius: 3.0 * scale,
trunk_len: 9.0 * scale,
trunk_radius: 2.0 * scale,
branch_child_len: 0.8,
branch_child_radius: 0.6,
leaf_radius: 3.0 * scale..4.0 * scale,
branch_child_radius: 0.75,
leaf_radius: 2.5 * log_scale..3.25 * log_scale,
straightness: 0.5,
max_depth: 4,
splits: 3,
split_range: 0.5..1.5,
max_depth: (4.0 + log_scale) as usize,
splits: 2.0..3.0,
split_range: 0.75..1.5,
branch_len_bias: 0.0,
leaf_vertical_scale: 1.0,
proportionality: 0.0,
}
}
pub fn pine(rng: &mut impl Rng) -> Self {
let scale = Lerp::lerp(1.0, 2.0, rng.gen::<f32>().powi(4));
pub fn pine(rng: &mut impl Rng, scale: f32) -> Self {
let scale = scale * (1.0 + rng.gen::<f32>().powi(4));
let log_scale = 1.0 + scale.log2().max(0.0);
Self {
trunk_len: 32.0 * scale,
trunk_radius: 1.5 * scale,
branch_child_len: 0.3,
trunk_radius: 1.25 * scale,
branch_child_len: 0.3 / scale,
branch_child_radius: 0.0,
leaf_radius: 2.0 * scale..2.5 * scale,
leaf_radius: 1.5 * log_scale..2.0 * log_scale,
straightness: 0.0,
max_depth: 1,
splits: 56,
splits: 50.0..70.0,
split_range: 0.2..1.2,
branch_len_bias: 0.75,
leaf_vertical_scale: 0.3,
@ -361,18 +363,19 @@ impl ProceduralTree {
let y_axis = dir.cross(x_axis).normalized();
let screw_shift = rng.gen_range(0.0..f32::consts::TAU);
for i in 0..config.splits {
let splits = rng.gen_range(config.splits.clone()).round() as usize;
for i in 0..splits {
let dist = Lerp::lerp(
i as f32 / (config.splits - 1) as f32,
i as f32 / (splits - 1) as f32,
rng.gen_range(0.0..1.0),
config.proportionality,
);
const PHI: f32 = 0.618;
const RAD_PER_BRANCH: f32 = f32::consts::TAU * PHI;
let screw = (screw_shift + dist * config.splits as f32 * RAD_PER_BRANCH).sin()
let screw = (screw_shift + dist * splits as f32 * RAD_PER_BRANCH).sin()
* x_axis
+ (screw_shift + dist * config.splits as f32 * RAD_PER_BRANCH).cos() * y_axis;
+ (screw_shift + dist * splits as f32 * RAD_PER_BRANCH).cos() * y_axis;
// Choose a point close to the branch to act as the target direction for the
// branch to grow in let split_factor =

View File

@ -23,7 +23,7 @@ pub use self::{
};
use crate::{
all::ForestKind,
all::*,
block::BlockGen,
civ::Place,
column::ColumnGen,
@ -45,6 +45,7 @@ use common::{
vol::RectVolSize,
};
use common_net::msg::WorldMapMsg;
use enum_iterator::IntoEnumIterator;
use noise::{
BasicMulti, Billow, Fbm, HybridMulti, MultiFractal, NoiseFn, RangeFunction, RidgedMulti,
Seedable, SuperSimplex, Worley,
@ -114,6 +115,7 @@ pub(crate) struct GenCtx {
pub cave_1_nz: SuperSimplex,
pub structure_gen: StructureGen2d,
pub big_structure_gen: StructureGen2d,
pub region_gen: StructureGen2d,
pub fast_turb_x_nz: FastNoise,
@ -516,7 +518,8 @@ impl WorldSim {
cave_0_nz: SuperSimplex::new().set_seed(rng.gen()),
cave_1_nz: SuperSimplex::new().set_seed(rng.gen()),
structure_gen: StructureGen2d::new(rng.gen(), 24, 8),
structure_gen: StructureGen2d::new(rng.gen(), 24, 12),
big_structure_gen: StructureGen2d::new(rng.gen(), 768, 512),
region_gen: StructureGen2d::new(rng.gen(), 400, 96),
humid_nz: Billow::new()
.set_octaves(9)
@ -1995,9 +1998,25 @@ impl WorldSim {
/// 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)> + '_ {
pub fn get_near_trees(&self, wpos: Vec2<i32>) -> impl Iterator<Item = TreeAttr> + '_ {
// Deterministic based on wpos
std::array::IntoIter::new(self.gen_ctx.structure_gen.get(wpos))
let normal_trees = std::array::IntoIter::new(self.gen_ctx.structure_gen.get(wpos))
.map(move |(pos, seed)| TreeAttr {
pos,
seed,
scale: 1.0,
forest_kind: self.get_wpos(pos).map_or(ForestKind::Oak, |c| c.forest_kind),
});
let giant_trees = std::array::IntoIter::new(self.gen_ctx.big_structure_gen.get(wpos))
.map(move |(pos, seed)| TreeAttr {
pos,
seed,
scale: 10.0,
forest_kind: ForestKind::Oak,
});
normal_trees.chain(giant_trees)
}
}
@ -2232,80 +2251,14 @@ impl SimChunk {
},
tree_density,
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 + CONFIG.desert_temp) / 2.0, 1.25),
(1.0, 2.0),
),
(
ForestKind::Acacia,
(0.0, 1.5),
(CONFIG.tropical_temp, 1.5),
(0.0, 1.0),
),
(
ForestKind::Baobab,
(0.0, 1.5),
(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),
),
(
ForestKind::Swamp,
((CONFIG.forest_hum + CONFIG.jungle_hum) / 2.0, 2.0),
((CONFIG.temperate_temp + CONFIG.snow_temp) / 2.0, 2.0),
(1.0, 2.5),
),
];
let env = Environment {
humid: humidity,
temp,
near_water: if river.is_lake() || river.near_river() { 1.0 } else { 0.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)
ForestKind::into_enum_iter()
.max_by_key(|fk| (fk.proclivity(&env) * 10000.0) as u32)
.unwrap() // Can't fail
},
spawn_rate: 1.0,

11
world/src/util/math.rs Normal file
View File

@ -0,0 +1,11 @@
use std::ops::Range;
use vek::*;
/// Return a value between 0 and 1 corresponding to how close to the centre of `range` `x` is.
/// The exact function used is left unspecified, but it shall have the shape of a bell-like curve.
/// This function is required to return `0` (or a value extremely close to `0`) when `x` is outside of `range`.
pub fn close(x: f32, range: Range<f32>) -> f32 {
let mean = (range.start + range.end) / 2.0;
let width = (range.end - range.start) / 2.0;
(1.0 - ((x - mean) / width).clamped(-1.0, 1.0).powi(2)).powi(2)
}

View File

@ -1,4 +1,5 @@
pub mod fast_noise;
pub mod math;
pub mod map_vec;
pub mod random;
pub mod sampler;