diff --git a/common/src/terrain/block.rs b/common/src/terrain/block.rs index 2758d3f42c..494ee2c0ad 100644 --- a/common/src/terrain/block.rs +++ b/common/src/terrain/block.rs @@ -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, diff --git a/world/src/all.rs b/world/src/all.rs index 5c9e4fbe4c..3020c4711f 100644 --- a/world/src/all.rs +++ b/world/src/all.rs @@ -4,6 +4,6 @@ pub enum ForestKind { Savannah, Oak, Pine, - SnowPine, + // SnowPine, Mangrove, } diff --git a/world/src/column/mod.rs b/world/src/column/mod.rs index 62c21f5b48..466987a82a 100644 --- a/world/src/column/mod.rs +++ b/world/src/column/mod.rs @@ -733,7 +733,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { ); // Columns near water get a humidity boost let humidity = Lerp::lerp( - Lerp::lerp(humidity, 1.0, 0.1), + Lerp::lerp(humidity, 1.0, 0.25), humidity, water_dist .map(|water_dist| water_dist / 20.0) @@ -879,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. @@ -947,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 @@ -1024,6 +1026,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { water_dist, path, cave, + snow_cover, chunk: sim_chunk, }) @@ -1052,6 +1055,7 @@ pub struct ColumnSample<'a> { pub water_dist: Option, pub path: Option<(f32, Vec2, Path, Vec2)>, pub cave: Option<(f32, Vec2, Cave, Vec2)>, + pub snow_cover: bool, pub chunk: &'a SimChunk, } diff --git a/world/src/config.rs b/world/src/config.rs index 5f8914d6e1..b3e841fce7 100644 --- a/world/src/config.rs +++ b/world/src/config.rs @@ -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, diff --git a/world/src/layer/tree.rs b/world/src/layer/tree.rs index 66f64def68..0a2505db18 100644 --- a/world/src/layer/tree.rs +++ b/world/src/layer/tree.rs @@ -6,7 +6,7 @@ use crate::{ Canvas, CONFIG, }; use common::{ - terrain::{structure::Structure, Block}, + terrain::{structure::Structure, Block, BlockKind}, vol::ReadVol, }; use lazy_static::lazy_static; @@ -18,7 +18,7 @@ lazy_static! { pub static ref OAK_STUMPS: Vec> = Structure::load_group("oak_stumps"); pub static ref PINES: Vec> = Structure::load_group("pines"); pub static ref PALMS: Vec> = Structure::load_group("palms"); - pub static ref SNOW_PINES: Vec> = Structure::load_group("snow_pines"); + // pub static ref SNOW_PINES: Vec> = Structure::load_group("snow_pines"); pub static ref ACACIAS: Vec> = Structure::load_group("acacias"); pub static ref FRUIT_TREES: Vec> = Structure::load_group("fruit_trees"); pub static ref BIRCHES: Vec> = Structure::load_group("birch"); @@ -77,7 +77,7 @@ pub fn apply_trees_to(canvas: &mut Canvas) { ForestKind::Oak if QUIRKY_RAND.get(seed) % 14 == 7 => &BIRCHES, ForestKind::Oak => &OAKS, ForestKind::Pine => &PINES, - ForestKind::SnowPine => &SNOW_PINES, + // ForestKind::SnowPine => &SNOW_PINES, ForestKind::Mangrove => &MANGROVE_TREES, } }; @@ -96,7 +96,8 @@ pub fn apply_trees_to(canvas: &mut Canvas) { }; let bounds = tree.model.get_bounds(); - for z in bounds.min.z..bounds.max.z { + let mut is_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) @@ -121,7 +122,19 @@ pub fn apply_trees_to(canvas: &mut Canvas) { col, Block::air, ) - .map(|block| canvas.set(wpos, block)); + .map(|block| { + if is_top && col.snow_cover && block.kind() == BlockKind::Leaves { + canvas.set( + wpos + Vec3::unit_z(), + Block::new(BlockKind::Snow, Rgb::new(210, 210, 255)), + ); + } + canvas.set(wpos, block); + is_top = false; + }) + .unwrap_or_else(|| { + is_top = true; + }); } } }); diff --git a/world/src/sim/mod.rs b/world/src/sim/mod.rs index fb5696f748..f951f991b6 100644 --- a/world/src/sim/mod.rs +++ b/world/src/sim/mod.rs @@ -2056,11 +2056,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]; @@ -2075,6 +2070,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); @@ -2138,7 +2145,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); @@ -2149,14 +2155,17 @@ 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) - * (1.0 - temp as f64) } as f32; + const MIN_TREE_HUM: f32 = 0.05; + // 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 @@ -2191,23 +2200,74 @@ impl SimChunk { 0.0 }, tree_density, - forest_kind: if temp > CONFIG.temperate_temp { - if temp > CONFIG.desert_temp { - if humidity > CONFIG.jungle_hum { + 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, 0.5), + (1.0, 2.0), + ), + ( + ForestKind::Savannah, + (CONFIG.desert_hum, 1.5), + (CONFIG.tropical_temp, 1.0), + (0.0, 1.0), + ), + ( + ForestKind::Oak, + (CONFIG.forest_hum, 1.5), + (CONFIG.temperate_temp, 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.desert_hum, 2.0), + (CONFIG.snow_temp, 2.5), + (0.0, 1.0), + ), + ]; + + candidates + .iter() + .min_by_key(|(_, (h, h_prev), (t, t_prev), (w, w_prev))| { + (Vec3::new( + (*h - humidity) / *h_prev, + (*t - temp) / *t_prev, + (*w - if river.near_water() { 1.0 } else { 0.0 }) / *w_prev, + ) + .map(|e| e * e) + .sum() + * 10000.0) as i32 + }) + .map(|c| c.0) + .unwrap() // Can't fail + }, + /* + if temp > CONFIG.temperate_temp { + if temp > CONFIG.tropical_temp { + if humidity > CONFIG.desert_hum && river.near_water() { // 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 { + } else { // Low but not desert humidity, so we should really have some other // terrain... ForestKind::Savannah - } else { - ForestKind::Savannah } - } else if temp > CONFIG.tropical_temp { + } else if temp > CONFIG.forest_hum { if humidity > CONFIG.jungle_hum { if tree_density > 0.0 { // println!("Mangrove: {:?}", wposf); @@ -2250,6 +2310,7 @@ impl SimChunk { ForestKind::Pine } }, + */ spawn_rate: 1.0, river, warp_factor: 1.0,