Distribution "fun."

This commit is contained in:
Joshua Yanovski 2019-08-20 22:48:22 +02:00
parent 405f55d725
commit 8c644e2bb1
6 changed files with 139 additions and 26 deletions

10
Cargo.lock generated
View File

@ -2814,6 +2814,14 @@ name = "static_assertions"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "statrs"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "stb_truetype"
version = "0.2.6"
@ -3172,6 +3180,7 @@ dependencies = [
"noise 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
"rand_chacha 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"statrs 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"vek 0.9.8 (registry+https://github.com/rust-lang/crates.io-index)",
"veloren-common 0.3.0",
"zerocopy 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
@ -3695,6 +3704,7 @@ dependencies = [
"checksum sphynx 0.1.0 (git+https://gitlab.com/veloren/sphynx.git?rev=11cdc7422568aaabd376c87242a60f636e68b40d)" = "<none>"
"checksum stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8"
"checksum static_assertions 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "c19be23126415861cb3a23e501d34a708f7f9b2183c5252d690941c2e69199d5"
"checksum statrs 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "835a1669368b3524d9214cfcb274a1f5e3b0820b5f275c1509bfb0cc67664636"
"checksum stb_truetype 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "69b7df505db8e81d54ff8be4693421e5b543e08214bd8d99eb761fcb4d5668ba"
"checksum stdweb 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ef5430c8e36b713e13b48a9f709cc21e046723fe44ce34587b73a830203b533e"
"checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"

View File

@ -13,6 +13,7 @@ lazy_static = "1.3.0"
rand = "0.7.0"
rand_chacha = "0.2.1"
zerocopy = "0.2.8"
statrs = "0.11.0"
[dev-dependencies]
minifb = { git = "https://github.com/emoon/rust_minifb.git" }

View File

@ -259,7 +259,8 @@ impl<'a> Sampler for ColumnGen<'a> {
let cliff = Rgb::lerp(cold_stone, warm_stone, marble);
let grass = Rgb::lerp(cold_grass, warm_grass, marble.powf(1.5).powf(1.0.sub(humidity)));
let moss = Rgb::lerp(cold_grass, dark_grass, marble.powf(1.5).powf(1.0.sub(humidity)));
let snow_moss = Rgb::lerp(cold_grass, dark_grass, marble.powf(1.5).powf(temp));
let moss = Rgb::lerp(dark_grass, cold_grass, marble.powf(1.5).powf(1.0.sub(humidity)));
let rainforest = Rgb::lerp(wet_grass, warm_grass, marble.powf(1.5).powf(1.0.sub(humidity)));
let sand = Rgb::lerp(beach_sand, desert_sand, marble);
@ -269,9 +270,18 @@ impl<'a> Sampler for ColumnGen<'a> {
marble_small.sub(0.5).mul(0.2).add(0.75).powf(0.667).powf(1.0.sub(humidity)),
);
// For below desert humidity, we are always sand or rock, depending on altitude.
let ground = Rgb::lerp(sand, cliff, alt.sub(CONFIG.mountain_scale * 0.25).div(CONFIG.mountain_scale * 0.125));
// From desert to forest humidity, we go from tundra to moss to grass to moss to sand,
// For below desert humidity, we are always sand or rock, depending on altitude and
// temperature.
let ground = Rgb::lerp(
Rgb::lerp(
sand,
dirt,
temp.sub(CONFIG.desert_temp).mul(0.5)
),
cliff,
alt.sub(CONFIG.mountain_scale * 0.25).div(CONFIG.mountain_scale * 0.125)
);
// From desert to forest humidity, we go from tundra to dirt to grass to moss to sand,
// depending on temperature.
let ground = Rgb::lerp(
ground,
@ -282,10 +292,12 @@ impl<'a> Sampler for ColumnGen<'a> {
// below snow_temp
tundra,
// snow_temp to 0
moss,
temp.sub(CONFIG.snow_temp)/*.div(CONFIG.snow_temp.neg())*/
.sub((marble - 0.5) * 0.05)
.mul(256.0)
dirt,
temp.sub(CONFIG.snow_temp)
.div(CONFIG.snow_temp.neg())
/*.sub((marble - 0.5) * 0.05)
.mul(256.0)*/
.mul(1.0)
),
// 0 to tropical_temp
grass,
@ -305,7 +317,7 @@ impl<'a> Sampler for ColumnGen<'a> {
.div(CONFIG.forest_hum.sub(CONFIG.desert_hum))
.mul(1.0)
);
// From forest to jungle humidity, we go from snow to moss to grass to tropics to sand
// From forest to jungle humidity, we go from snow to dark grass to grass to tropics to sand
// depending on temperature.
let ground = Rgb::lerp(
ground,
@ -316,7 +328,7 @@ impl<'a> Sampler for ColumnGen<'a> {
// below snow_temp
snow,
// snow_temp to 0
moss,
snow_moss,
temp.sub(CONFIG.snow_temp)/*.div(CONFIG.snow_temp.neg())*/
.sub((marble - 0.5) * 0.05)
.mul(256.0)

View File

@ -13,9 +13,9 @@ pub const CONFIG: Config = Config {
sea_level: 140.0,
mountain_scale: 1000.0,
snow_temp: -0.4,
tropical_temp: 0.25,
tropical_temp: 0.2,
desert_temp: 0.45,
desert_hum: 0.35,
desert_hum: 0.2,
forest_hum: 0.5,
jungle_hum: 0.6,
jungle_hum: 0.8,
};

View File

@ -1,5 +1,5 @@
#![deny(unsafe_code)]
#![feature(euclidean_division, bind_by_move_pattern_guards, option_flattening)]
#![feature(const_generics, euclidean_division, bind_by_move_pattern_guards, option_flattening)]
mod all;
mod block;

View File

@ -17,6 +17,13 @@ use common::{
use noise::{BasicMulti, Billow, HybridMulti, MultiFractal, NoiseFn, RidgedMulti, Seedable, SuperSimplex};
use rand::{Rng, SeedableRng};
use rand_chacha::ChaChaRng;
use statrs::distribution::{
InverseGamma,
LogNormal,
Gamma,
Normal,
Univariate,
};
use std::{
f32,
ops::{Add, Div, Mul, Neg, Sub},
@ -25,6 +32,34 @@ use vek::*;
pub const WORLD_SIZE: Vec2<usize> = Vec2 { x: 1024, y: 1024 };
/// Computes the cumulative distribution function of the weighted sum of k independent,
/// uniformly distributed random variables between 0 and 1. For each variable i, we use weights[i]
/// as the weight to give samples[i] (the weights should all be positive).
///
/// If the precondition is met, the distribution of the result of calling this function will be
/// uniformly distributed while preserving the same information that was in the original average.
///
/// NOTE: For N > 33 the function will no longer return correct results since we will overflow u32.
fn cdf_irwin_hall<const N : usize>(weights: &[f32; N], samples: [f32; N]) -> f32 {
// Take the average of the weights
// (to scale the weights down so their sum is in the (0..=N) range).
let avg = weights.iter().sum::<f32>() / N as f32;
// Take the sum.
let x : f32 =
weights.iter().zip(samples.iter()).map(|(weight, sample)| weight / avg * sample).sum();
// CDF = 1 / N! * Σ{k = 0 to floor(x)} ((-1)^k (N choose k) (x - k) ^ N)
let mut binom = 1; // (-1)^0 * (n choose 0) = 1 * 1 = 1
let mut y = x.powi(N as i32); // 1 * (x - 0)^N = x ^N
// 1..floor(x)
for k in (1..=x.floor() as i32) {
// (-1)^k (N choose k) = ((-1)^(k-1) (N choose (k - 1))) * -(N + 1 - k) / k for k ≥ 1.
binom *= -(N as i32 + 1 - k) / k;
y += binom as f32 * (x - k as f32).powi(N as i32);
}
// Remember to multiply by 1 / N! at the end.
y / (1..=N as i32).product::<i32>() as f32
}
pub(crate) struct GenCtx {
pub turb_x_nz: SuperSimplex,
pub turb_y_nz: SuperSimplex,
@ -379,13 +414,16 @@ impl SimChunk {
// "Base" of the chunk, to be multiplied by CONFIG.mountain_scale (multiplied value is
// from -0.25 * (CONFIG.mountain_scale * 1.1) to 0.25 * (CONFIG.mountain_scale * 0.9),
// but value here is from -0.275 to 0.225).
let alt_base = (gen_ctx.alt_nz.get((wposf.div(12_000.0)).into_array()) as f32)
let alt_base_pre = (gen_ctx.alt_nz.get((wposf.div(12_000.0)).into_array()) as f32);
let alt_base = alt_base_pre
.sub(0.1)
.mul(0.25);
// Extension upwards from the base. A positive number from 0 to 1 curved to be maximal at
// 0.
let alt_main = (gen_ctx.alt_nz.get((wposf.div(2_000.0)).into_array()) as f32)
let alt_main_pre = (gen_ctx.alt_nz.get((wposf.div(2_000.0)).into_array()) as f32);
let alt_main = alt_main_pre
.abs()
.powf(1.35);
@ -519,19 +557,33 @@ impl SimChunk {
// (2)^(-1)
//
// Now we just take a (currently) unweighted average of our randomly generated base humidity
// Take the weighted average of our randomly generated base humidity, the scaled
// negative altitude, and other random variable (to add some noise) to yield the
// final humidity.
const WEIGHTS : [f32; 4] = [3.0, 1.0, 1.0, 1.0];
let humidity = cdf_irwin_hall(
&WEIGHTS,
[humid_base,
alt_main_pre.mul(0.5).add(0.5),
alt_base_pre.mul(0.5).add(0.5),
(gen_ctx.small_nz.get((wposf.div(500.0)).into_array()) as f32)
.mul(0.5)
.add(0.5)]
);
/* // Now we just take a (currently) unweighted average of our randomly generated base humidity
// (from scaled to be from 0 to 1) and our randomly generated "base" humidity. We can
// adjust this weighting factor as desired.
let humid_weight = 3.0;
let humid_alt_weight = 1.0;
let humidity =
humid_base.mul(humid_weight)
.add(humid_alt.mul(humid_alt_weight)
.add(humid_alt
.mul(humid_alt_weight)
// Adds some noise to the humidity effect of altitude to dampen it.
.mul(gen_ctx.small_nz.get((wposf.div(10240.0)).into_array()) as f32)
.mul(0.5)
.add(0.5))
.div(humid_weight + humid_alt_weight);
.mul((gen_ctx.small_nz.get((wposf.div(500.0)).into_array()) as f32)
.mul(0.5)
.add(0.5)))
.div(humid_weight + humid_alt_weight); */
let temp_base =
gen_ctx.temp_nz.get((wposf.div(12000.0)).into_array()) as f32;
@ -540,13 +592,13 @@ impl SimChunk {
// distribution different for temperature as well.
let temp_alt_sigma = -0.0625;
let temp_alt_2s = 3.0f32.sqrt().mul(f32::consts::FRAC_2_PI).mul(temp_alt_sigma);
let temp_alt_mu = 0.0;
let temp_alt_mu = 0.0625;
// Scaled to [-1, 1] already.
let temp_alt = humid_alt_pre
.sub(temp_alt_mu)
.div(temp_alt_2s)
.tanh();
let temp_weight = 4.0;
let temp_weight = 2.0;
let temp_alt_weight = 1.0;
let temp =
temp_base.mul(temp_weight)
@ -591,9 +643,9 @@ impl SimChunk {
.add(0.05)
.max(0.0)
.min(1.0)
.mul(0.5)
.mul(0.4)
// Tree density should go (by a lot) with humidity.
.add(humidity.mul(0.5))
.add(humidity.mul(0.6))
// No trees in the ocean (currently), no trees in true deserts.
.mul(if alt > CONFIG.sea_level + 5.0 && humidity > CONFIG.desert_hum {
1.0
@ -602,6 +654,30 @@ impl SimChunk {
})
.max(0.0);
// let humid_normal = InverseGamma::new(4.0, 0.1).unwrap();
// let humid_normal = LogNormal::new(0.0, 0.1).unwrap();
let humid_normal = Gamma::new(1.0, 0.5).unwrap();
// let humid_normal = Gamma::new(0.1, 1.0).unwrap();
// let humid_normal = Normal::new(0.5, 0.05).unwrap();
/*if humid_normal.cdf(humid_base as f64) > 0.9 *//* {
println!("HIGH HUMIDITY: {:?}", humid_base);
} */
if pos == Vec2::new(1023, 1023) {
let mut noise = (0..1024*1024).map( |i| {
let wposf = Vec2::new(i as f64 / 1024.0, i as f64 % 1024.0);
gen_ctx.humid_nz.get(wposf.div(1024.0).into_array()) as f32
} ).collect::<Vec<_>>();
noise.sort_unstable_by( |f, g| f.partial_cmp(g).unwrap() );
for (k, f) in noise.iter().enumerate().step_by(1024 * 1024 / 100) {
println!("{:?}%: {:?}, ", k / (1024 * 1024 / 100), f);
}
}
/* if alt_main_pre.mul(0.5).add(0.5) > 0.7 {
println!("HIGH: {:?}", alt_main_pre);
} */
/* if humidity > CONFIG.jungle_hum {
println!("JUNGLE");
} */
Self {
chaos,
@ -627,14 +703,22 @@ impl SimChunk {
// Forests in desert temperatures with extremely high humidity
// should probably be different from palm trees, but we use them
// for now.
/* /*if tree_density > 0.0 */{
println!("Palm trees (jungle): {:?}, altitude: {:?}, humidity: {:?}, temperature: {:?}, density: {:?}", wposf, alt, humidity, temp, tree_density);
} */
ForestKind::Palm
} else if humidity > CONFIG.forest_hum {
/* /*if tree_density > 0.0 */{
println!("Palm trees (forest): {:?}, altitude: {:?}, humidity: {:?}, temperature: {:?}, density: {:?}", wposf, alt, humidity, temp, tree_density);
} */
ForestKind::Palm
} else {
// Low but not desert humidity, so we should really have some other
// terrain...
/* if humidity < CONFIG.desert_hum {
println!("True desert: {:?}, altitude: {:?}, humidity: {:?}, temperature: {:?}, density: {:?}", wposf, alt, humidity, temp, tree_density);
} else {
println!("Savannah (desert): {:?}, altitude: {:?}, humidity: {:?}, temperature: {:?}, density: {:?}", wposf, alt, humidity, temp, tree_density);
} */
ForestKind::Savannah
}
@ -665,6 +749,9 @@ impl SimChunk {
// Moderate climate, moderate humidity.
ForestKind::Oak
} else {
/* if humidity < CONFIG.desert_hum {
println!("True desert: {:?}, altitude: {:?}, humidity: {:?}, temperature: {:?}, density: {:?}", wposf, alt, humidity, temp, tree_density);
} */
// With moderate temperature and low humidity, we should probably see
// something different from savannah, but oh well...
ForestKind::Savannah
@ -679,6 +766,9 @@ impl SimChunk {
} */
ForestKind::SnowPine
} else {
/* if humidity < CONFIG.desert_hum {
println!("True desert: {:?}, altitude: {:?}, humidity: {:?}, temperature: {:?}, density: {:?}", wposf, alt, humidity, temp, tree_density);
} */
ForestKind::Pine
}
},