From 24c75f7cc3247e858250c0933ec1054a3c9ba975 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Sun, 7 Feb 2021 19:55:13 +0000 Subject: [PATCH] Experimental giant mother trees --- assets/voxygen/shaders/particle-vert.glsl | 2 +- common/src/terrain/block.rs | 4 +- voxygen/src/mesh/terrain.rs | 38 ++++---- voxygen/src/render/renderer.rs | 2 +- world/Cargo.toml | 1 + world/src/all.rs | 77 +++++++++++++++- world/src/column/mod.rs | 7 ++ world/src/layer/tree.rs | 59 ++++++------ world/src/sim/mod.rs | 107 ++++++---------------- world/src/util/math.rs | 11 +++ world/src/util/mod.rs | 1 + 11 files changed, 180 insertions(+), 129 deletions(-) create mode 100644 world/src/util/math.rs diff --git a/assets/voxygen/shaders/particle-vert.glsl b/assets/voxygen/shaders/particle-vert.glsl index f33ece8ede..c950872e25 100644 --- a/assets/voxygen/shaders/particle-vert.glsl +++ b/assets/voxygen/shaders/particle-vert.glsl @@ -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) { diff --git a/common/src/terrain/block.rs b/common/src/terrain/block.rs index 7c7a4e0168..52552b41e7 100644 --- a/common/src/terrain/block.rs +++ b/common/src/terrain/block.rs @@ -196,8 +196,8 @@ impl Block { #[inline] pub fn get_max_sunlight(&self) -> Option { 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, } diff --git a/voxygen/src/mesh/terrain.rs b/voxygen/src/mesh/terrain.rs index 242ee59fed..09b8e79549 100644 --- a/voxygen/src/mesh/terrain.rs +++ b/voxygen/src/mesh/terrain.rs @@ -69,25 +69,23 @@ fn calc_light + 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 + 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 + 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| light(pos + range.min); + let get_light = |_: &mut (), pos: Vec3| 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| glow(pos + range.min); let get_color = |_: &mut (), pos: Vec3| flat_get(pos).get_color().unwrap_or(Rgb::zero()); diff --git a/voxygen/src/render/renderer.rs b/voxygen/src/render/renderer.rs index f1ae11e340..15ffdb7761 100644 --- a/voxygen/src/render/renderer.rs +++ b/voxygen/src/render/renderer.rs @@ -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) diff --git a/world/Cargo.toml b/world/Cargo.toml index 60805141ab..9c123320c9 100644 --- a/world/Cargo.toml +++ b/world/Cargo.toml @@ -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" diff --git a/world/src/all.rs b/world/src/all.rs index fb44983678..2ce6f1ccd8 100644 --- a/world/src/all.rs +++ b/world/src/all.rs @@ -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 { + 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 { + 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> { + 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, + pub seed: u32, + pub scale: f32, + pub forest_kind: ForestKind, +} diff --git a/world/src/column/mod.rs b/world/src/column/mod.rs index 64bb8bafc0..9a5061f2db 100644 --- a/world/src/column/mod.rs +++ b/world/src/column/mod.rs @@ -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) diff --git a/world/src/layer/tree.rs b/world/src/layer/tree.rs index ba2d2fb5e7..13e28a8fe4 100644 --- a/world/src/layer/tree.rs +++ b/world/src/layer/tree.rs @@ -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, /// 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::().powi(4)); + pub fn oak(rng: &mut impl Rng, scale: f32) -> Self { + let scale = scale * (1.0 + rng.gen::().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::().powi(4)); + pub fn pine(rng: &mut impl Rng, scale: f32) -> Self { + let scale = scale * (1.0 + rng.gen::().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 = diff --git a/world/src/sim/mod.rs b/world/src/sim/mod.rs index 4ec3e89b3e..5019db6f52 100644 --- a/world/src/sim/mod.rs +++ b/world/src/sim/mod.rs @@ -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) -> impl Iterator, u32)> + '_ { + pub fn get_near_trees(&self, wpos: Vec2) -> impl Iterator + '_ { // 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, diff --git a/world/src/util/math.rs b/world/src/util/math.rs new file mode 100644 index 0000000000..857dd8c018 --- /dev/null +++ b/world/src/util/math.rs @@ -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 { + 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) +} diff --git a/world/src/util/mod.rs b/world/src/util/mod.rs index a04a8d2bb3..f80f21e0b7 100644 --- a/world/src/util/mod.rs +++ b/world/src/util/mod.rs @@ -1,4 +1,5 @@ pub mod fast_noise; +pub mod math; pub mod map_vec; pub mod random; pub mod sampler;