Reverting changes except to humidity and temperature

noise.
This commit is contained in:
Joshua Yanovski 2019-11-07 21:25:30 +01:00
parent 28d0afbfb6
commit 1b864887e7
5 changed files with 204 additions and 90 deletions

View File

@ -21,21 +21,50 @@ fn main() {
while win.is_open() {
let mut buf = vec![0; W * H];
const QUADRANTS : usize = 4;
let mut quads = [[0u32; QUADRANTS]; QUADRANTS];
let mut rivers = 0u32;
let mut lakes = 0u32;
let mut oceans = 0u32;
for i in 0..W {
for j in 0..H {
let pos = focus + Vec2::new(i as i32, j as i32) * scale;
let (alt, water_alt, river_kind) = sampler
let (alt, water_alt, humidity, temperature, river_kind) = sampler
.get(pos)
.map(|sample| (sample.alt, sample.water_alt, sample.river.river_kind))
.unwrap_or((CONFIG.sea_level, CONFIG.sea_level, None));
let alt = ((alt - CONFIG.sea_level) / CONFIG.mountain_scale)
.map(|sample| (sample.alt, sample.water_alt, sample.humidity, sample.temp, sample.river.river_kind))
.unwrap_or((CONFIG.sea_level, CONFIG.sea_level, 0.0, 0.0, None));
let humidity = humidity
.min(1.0)
.max(0.0);
let temperature = temperature
.min(1.0)
.max(-1.0)
* 0.5 + 0.5;
let water_alt = ((alt.max(water_alt) - CONFIG.sea_level) / CONFIG.mountain_scale)
.min(1.0)
.max(0.0);
let alt = ((alt - CONFIG.sea_level) / CONFIG.mountain_scale)
.min(1.0)
.max(0.0);
let quad = |x: f32| ((x as f64 * QUADRANTS as f64).floor() as usize).min(QUADRANTS - 1);
if river_kind.is_none() || humidity != 0.0 {
quads[quad(humidity)][quad(temperature)] += 1;
}
match river_kind {
Some(RiverKind::River { .. }) => {
rivers += 1;
},
Some(RiverKind::Lake { .. }) => {
lakes += 1;
},
Some(RiverKind::Ocean { .. }) => {
oceans += 1;
},
None => {},
}
buf[j * W + i] = match river_kind {
Some(RiverKind::Ocean) => u32::from_le_bytes([64, 32, 0, 255]),
Some(RiverKind::Lake { .. }) => u32::from_le_bytes([
@ -50,12 +79,30 @@ fn main() {
0,
255,
]),
None => u32::from_le_bytes([0, (alt * 255.0) as u8, 0, 255]),
None => u32::from_le_bytes([
(/*alt * *//*(1.0 - humidity)*/(alt * humidity).sqrt()/*temperature*/ * 255.0) as u8,
(/*alt*//*alt*//* * humidity*//*alt * 255.0*//*humidity*/alt * 255.0) as u8,
(/*alt*//*alt * *//*(1.0 - humidity)*/(alt * temperature).sqrt() * 255.0) as u8,
255,
]),
};
}
}
let spd = 32;
if win.is_key_down(minifb::Key::P) {
println!("\
Land(adjacent): (X = temp, Y = humidity): {:?}\n\
Rivers: {:?}\n\
Lakes: {:?}\n\
Oceans: {:?}\n\
Total water: {:?}\n\
Total land(adjacent): {:?}",
quads, rivers, lakes, oceans,
rivers + lakes + oceans,
quads.iter().map( |x| x.iter().sum::<u32>() ).sum::<u32>()
);
}
if win.is_key_down(minifb::Key::W) {
focus.y -= spd * scale;
}

View File

@ -829,21 +829,36 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
//let humidity = humidity.add((marble - 0.5) * 0.10);
// Colours
let cold_grass = Rgb::new(0.1, 0.5, 0.1);
let warm_grass = Rgb::new(0.1, 0.9, 0.2);
let dark_grass = Rgb::new(0.1, 0.3, 0.2);
let wet_grass = Rgb::new(0.1, 0.5, 0.5);
let cold_stone = Rgb::new(0.5, 0.5, 0.5);
//let warm_stone = Rgb::new(0.6, 0.6, 0.5);
let warm_stone = Rgb::new(0.6, 0.5, 0.1);
let cold_grass = Rgb::new(0.0, 0.5, 0.25);
// let cold_grass = Rgb::new(0.1, 0.5, 0.1);
let warm_grass = Rgb::new(0.4, 0.8, 0.0);
// let warm_grass = Rgb::new(0.1, 0.9, 0.2);
let dark_grass = Rgb::new(0.15, 0.4, 0.1);
// let dark_grass = Rgb::new(0.1, 0.3, 0.2);
let wet_grass = Rgb::new(0.1, 0.8, 0.2);
// let wet_grass = Rgb::new(0.1, 0.5, 0.5);
let cold_stone = Rgb::new(0.57, 0.67, 0.8);
// let cold_stone = Rgb::new(0.5, 0.5, 0.5);
let warm_stone = Rgb::new(0.77, 0.77, 0.64);
// //let warm_stone = Rgb::new(0.6, 0.6, 0.5);
// let warm_stone = Rgb::new(0.6, 0.5, 0.1);
let beach_sand = Rgb::new(0.9, 0.82, 0.6);
let desert_sand = Rgb::new(0.7, 0.7, 0.4);
let snow = Rgb::new(0.0, 0.0, 0.1);
let stone_col = Rgb::new(152, 98, 16);
let dirt = Lerp::lerp(
let desert_sand = Rgb::new(0.95, 0.75, 0.5);
// let desert_sand = Rgb::new(0.7, 0.7, 0.4);
let snow = Rgb::new(0.8, 0.85, 1.0);
// let snow = Rgb::new(0.0, 0.0, 0.1);
// let stone_col = Rgb::new(152, 98, 16);
let stone_col = Rgb::new(195, 187, 201);
/*let dirt = Lerp::lerp(
Rgb::new(0.4, 0.4, 0.4),
Rgb::new(0.4, 0.4, 0.4),
marble,
);*/
let dirt = Lerp::lerp(
Rgb::new(0.075, 0.07, 0.3),
Rgb::new(0.75, 0.55, 0.1),
marble,
);
let tundra = Lerp::lerp(snow, Rgb::new(0.01, 0.3, 0.0), 0.4 + marble * 0.6);
let dead_tundra = Lerp::lerp(warm_stone, Rgb::new(0.3, 0.12, 0.2), marble);
@ -881,7 +896,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
sand,
temp.sub(CONFIG.snow_temp)
.div(CONFIG.desert_temp.sub(CONFIG.snow_temp))
.mul(4.5),
.mul(/*4.5*/0.5),
),
cliff,
alt.sub(CONFIG.mountain_scale * 0.25)
@ -902,7 +917,8 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
.div(CONFIG.snow_temp.neg())
/*.sub((marble - 0.5) * 0.05)
.mul(256.0)*/
.mul(2.0),
.mul(1.0),
// .mul(2.0),
),
// 0 to tropical_temp
grass,
@ -912,18 +928,21 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
moss,
temp.sub(CONFIG.tropical_temp)
.div(CONFIG.desert_temp.sub(CONFIG.tropical_temp))
.mul(2.0),
.mul(1.0),
// .mul(2.0),
),
// above desert_temp
sand,
temp.sub(CONFIG.desert_temp)
.div(1.0 - CONFIG.desert_temp)
.mul(2.0),
.mul(4.0),
// .mul(2.0),
),
humidity
.sub(CONFIG.desert_hum)
.div(CONFIG.forest_hum.sub(CONFIG.desert_hum))
.mul(2.0),
.mul(1.0),
// .mul(2.0),
);
// From forest to jungle humidity, we go from snow to dark grass to grass to tropics to sand
// depending on temperature.
@ -941,18 +960,21 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
tropical,
temp.sub(CONFIG.tropical_temp)
.div(CONFIG.desert_temp.sub(CONFIG.tropical_temp))
.mul(2.0),
.mul(1.0),
// .mul(2.0),
),
// above desert_temp
sand,
temp.sub(CONFIG.desert_temp)
.div(1.0 - CONFIG.desert_temp)
.mul(2.0),
.mul(4.0),
// .mul(2.0),
),
humidity
.sub(CONFIG.forest_hum)
.div(CONFIG.jungle_hum.sub(CONFIG.forest_hum))
.mul(2.0),
.mul(1.0),
// .mul(2.0),
);
// From jungle humidity upwards, we go from snow to grass to rainforest to tropics to sand.
let ground = Rgb::lerp(
@ -969,13 +991,15 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
tropical,
temp.sub(CONFIG.tropical_temp)
.div(CONFIG.desert_temp.sub(CONFIG.tropical_temp))
.mul(2.0),
.mul(4.0),
// .mul(2.0),
),
// above desert_temp
sand,
temp.sub(CONFIG.desert_temp)
.div(1.0 - CONFIG.desert_temp)
.mul(2.0),
.mul(4.0),
// .mul(2.0),
),
humidity.sub(CONFIG.jungle_hum).mul(1.0),
);
@ -1046,7 +1070,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
// Beach
((ocean_level - 1.0) / 2.0).max(0.0),
),
sub_surface_color: warm_grass,
sub_surface_color: /*warm_grass*/dirt,
tree_density,
forest_kind: sim_chunk.forest_kind,
close_structures: self.gen_close_structures(wpos),

View File

@ -48,7 +48,7 @@ pub const CONFIG: Config = Config {
mountain_scale: 2048.0,
snow_temp: -0.6,
tropical_temp: 0.2,
desert_temp: 0.9,
desert_temp: 0.6,
desert_hum: 0.15,
forest_hum: 0.5,
jungle_hum: 0.85,

View File

@ -112,26 +112,30 @@ impl WorldSim {
let gen_ctx = GenCtx {
turb_x_nz: SuperSimplex::new().set_seed(rng.gen()),
turb_y_nz: SuperSimplex::new().set_seed(rng.gen()),
chaos_nz: RidgedMulti::new().set_octaves(2).set_seed(rng.gen()),
chaos_nz: RidgedMulti::new().set_octaves(7).set_seed(rng.gen()),
hill_nz: SuperSimplex::new().set_seed(rng.gen()),
alt_nz: HybridMulti::new()
.set_octaves(2)
.set_octaves(8)
.set_persistence(0.1)
.set_seed(rng.gen()),
//temp_nz: SuperSimplex::new().set_seed(rng.gen()),
temp_nz: Fbm::new()
.set_octaves(8)
.set_persistence(0.9)
.set_octaves(6)
.set_persistence(0.5)
.set_frequency(/*4.0 / /*(1024.0 * 4.0/* * 8.0*/)*//*32.0*/((1 << 6) * (WORLD_SIZE.x)) as f64*/1.0 / (((1 << 6) * 64) as f64))
// .set_frequency(1.0 / 1024.0)
// .set_frequency(1.0 / (1024.0 * 8.0))
.set_lacunarity(2.0)
.set_seed(rng.gen()),
small_nz: BasicMulti::new().set_octaves(1).set_seed(rng.gen()),
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.1).set_seed(rng.gen()),
warp_nz: FastNoise::new(rng.gen()), //BasicMulti::new().set_octaves(1).set_seed(gen_seed()),
cliff_nz: HybridMulti::new().set_persistence(0.3).set_seed(rng.gen()),
warp_nz: FastNoise::new(rng.gen()), //BasicMulti::new().set_octaves(3).set_seed(gen_seed()),
tree_nz: BasicMulti::new()
.set_octaves(1)
.set_persistence(0.1)
.set_octaves(12)
.set_persistence(0.75)
.set_seed(rng.gen()),
cave_0_nz: SuperSimplex::new().set_seed(rng.gen()),
cave_1_nz: SuperSimplex::new().set_seed(rng.gen()),
@ -141,7 +145,7 @@ impl WorldSim {
cliff_gen: StructureGen2d::new(rng.gen(), 80, 56),
humid_nz: Billow::new()
.set_octaves(9)
.set_persistence(0.7)
.set_persistence(0.4)
.set_frequency(0.2)
// .set_octaves(6)
// .set_persistence(0.5)
@ -159,6 +163,7 @@ impl WorldSim {
.set_persistence(0.9)
.set_seed(rng.gen());
// No NaNs in these uniform vectors, since the original noise value always returns Some.
let ((alt_base, _), (chaos, _)) = rayon::join(
|| {
uniform_noise(|_, wposf| {
@ -192,7 +197,7 @@ impl WorldSim {
.get((wposf.div(400.0)).into_array())
.min(1.0)
.max(-1.0)
.mul(0.9))
.mul(0.3))
.add(0.3)
.max(0.0);
@ -232,6 +237,8 @@ impl WorldSim {
// We ignore sea level because we actually want to be relative to sea level here and want
// things in CONFIG.mountain_scale units, but otherwise this is a correct altitude
// calculation. Note that this is using the "unadjusted" temperature.
//
// No NaNs in these uniform vectors, since the original noise value always returns Some.
let (alt_old, alt_old_inverse) = uniform_noise(|posi, wposf| {
// This is the extension upwards from the base added to some extra noise from -1 to
// 1.
@ -289,7 +296,7 @@ impl WorldSim {
// = [-.3675, .3325] + ([-0.5785, 0.7345])
// = [-0.946, 1.067]
Some(
((alt_base[posi].1 + alt_main.mul((chaos[posi].1 as f64).powf(1.2)))
((alt_base[posi].1 + alt_main.mul((chaos[posi].1 as f64).powf(1.2)/*0.25*/))
.mul(map_edge_factor(posi) as f64)
.add(
(CONFIG.sea_level as f64)
@ -301,9 +308,28 @@ impl WorldSim {
)
});
// Calculate oceans.
let old_height = |posi: usize| alt_old[posi].1;
let is_ocean = get_oceans(old_height);
let is_ocean_fn = |posi: usize| is_ocean[posi];
/* // Recalculate altitudes without oceans.
// NaNs in these uniform vectors wherever pure_water() returns true.
let (alt_old_no_ocean, /*alt_old_inverse*/_) = uniform_noise(|posi, _| {
if is_ocean_fn(posi) {
None
} else {
Some(old_height(posi)/*.abs()*/)
}
});
let old_height_uniform = |posi: usize| alt_old_no_ocean[posi].0;
let alt_old_min_uniform = 0.0;
let alt_old_max_uniform = 1.0; */
// Find the minimum and maximum original altitudes.
// NOTE: Will panic if there is no land, and will not work properly if the minimum and
// maximum land altitude are identical (will most likely panic later).
let old_height_uniform = |posi: usize| alt_old[posi].0;
let (alt_old_min_index, _alt_old_min) = alt_old_inverse
.iter()
.copied()
@ -313,12 +339,6 @@ impl WorldSim {
let alt_old_min_uniform = alt_old[alt_old_min_index].0;
let alt_old_max_uniform = alt_old[alt_old_max_index].0;
// Calculate oceans.
let old_height = |posi: usize| alt_old[posi].1;
let old_height_uniform = |posi: usize| alt_old[posi].0;
let is_ocean = get_oceans(old_height);
let is_ocean_fn = |posi: usize| is_ocean[posi];
// Perform some erosion.
let max_erosion_per_delta_t = 32.0 / CONFIG.mountain_scale as f64;
@ -349,9 +369,12 @@ impl WorldSim {
|posi| {
let height = ((old_height_uniform(posi) - alt_old_min_uniform) as f64
/ (alt_old_max_uniform - alt_old_min_uniform) as f64)
/*.mul(1.0 - f64::EPSILON * 0.5)
.add(f64::EPSILON * 0.5);*/
.max(1e-7 / CONFIG.mountain_scale as f64)
.min(1.0f64 - 1e-7);
let height = erosion_factor(height);
assert!(height >= 0.0);
let height = height
.mul(max_erosion_per_delta_t * 7.0 / 8.0)
.add(max_erosion_per_delta_t / 8.0);
@ -473,25 +496,31 @@ impl WorldSim {
true
};
let (pure_flux, _) = uniform_noise(|posi, _| {
if pure_water(posi) {
None
} else {
Some(flux_old[posi])
}
});
let ((alt_no_water, _), ((temp_base, _), (humid_base, _))) = rayon::join(
// NaNs in these uniform vectors wherever pure_water() returns true.
let (((alt_no_water, _), (pure_flux, _)), ((temp_base, _), (humid_base, _))) = rayon::join(
|| {
uniform_noise(|posi, _| {
if pure_water(posi) {
None
} else {
// A version of alt that is uniform over *non-water* (or land-adjacent water)
// chunks.
Some(alt[posi])
}
})
rayon::join(
|| {
uniform_noise(|posi, _| {
if pure_water(posi) {
None
} else {
// A version of alt that is uniform over *non-water* (or land-adjacent water)
// chunks.
Some(alt[posi])
}
})
},
|| {
uniform_noise(|posi, _| {
if pure_water(posi) {
None
} else {
Some(flux_old[posi])
}
})
},
)
},
|| {
rayon::join(
@ -501,7 +530,7 @@ impl WorldSim {
None
} else {
// -1 to 1.
Some(gen_ctx.temp_nz.get((wposf.div(12000.0)).into_array()) as f32)
Some(gen_ctx.temp_nz.get((wposf/*.div(12000.0)*/).into_array()) as f32)
}
})
},
@ -1007,15 +1036,18 @@ impl SimChunk {
let _map_edge_factor = map_edge_factor(posi);
let (_, chaos) = gen_cdf.chaos[posi];
let (humid_uniform, _) = gen_cdf.humid_base[posi];
let alt_pre = gen_cdf.alt[posi];
let water_alt_pre = gen_cdf.water_alt[posi];
let downhill_pre = gen_cdf.dh[posi];
let (flux_uniform, _) = gen_cdf.pure_flux[posi];
let flux = gen_cdf.flux[posi];
let river = gen_cdf.rivers[posi].clone();
// Can have NaNs in non-uniform part where pure_water returned true. We just test one of
// the four in order to find out whether this is the case.
let (flux_uniform, /*flux_non_uniform*/_) = gen_cdf.pure_flux[posi];
let (alt_uniform, _) = gen_cdf.alt_no_water[posi];
let (temp_uniform, _) = gen_cdf.temp_base[posi];
let river = gen_cdf.rivers[posi].clone();
let (humid_uniform, _) = gen_cdf.humid_base[posi];
/* // Vertical difference from the equator (NOTE: "uniform" with much lower granularity than
// other uniform quantities, but hopefully this doesn't matter *too* much--if it does, we
@ -1030,25 +1062,36 @@ impl SimChunk {
// Take the weighted average of our randomly generated base humidity, the scaled
// negative altitude, and the calculated water flux over this point in order to compute
// humidity.
const HUMID_WEIGHTS: [f32; 3] = [4.0, 0.1, 0.1];
let humidity = cdf_irwin_hall(
&HUMID_WEIGHTS,
[humid_uniform, flux_uniform, 1.0 - alt_uniform],
);
const HUMID_WEIGHTS: [f32; /*3*/2] = [4.0, 1.0/*, 1.0*/];
let humidity = /*if flux_non_uniform.is_nan() {
0.0
} else */{
cdf_irwin_hall(
&HUMID_WEIGHTS,
[humid_uniform, flux_uniform/*, 1.0 - alt_uniform*/],
)
};
// We also correlate temperature negatively with altitude and absolute latitude, using
// different weighting than we use for humidity.
const TEMP_WEIGHTS: [f32; 2] = [2.0, 16.0 /*, 1.0*/];
let temp = cdf_irwin_hall(
&TEMP_WEIGHTS,
[
temp_uniform,
1.0 - alt_uniform, /* 1.0 - abs_lat_uniform*/
],
)
const TEMP_WEIGHTS: [f32; 2] = [/*1.5, */1.0, 2.0];
let temp = /*if flux_non_uniform.is_nan() {
0.0
} else */{
cdf_irwin_hall(
&TEMP_WEIGHTS,
[
temp_uniform,
1.0 - alt_uniform, /* 1.0 - abs_lat_uniform*/
],
)
}
// Convert to [-1, 1]
.sub(0.5)
.mul(2.0);
/* if (temp - (1.0 - alt_uniform).sub(0.5).mul(2.0)).abs() >= 1e-7 {
panic!("Halp!");
} */
let mut alt = CONFIG.sea_level.add(alt_pre.mul(CONFIG.mountain_scale));
let water_alt = CONFIG

View File

@ -74,13 +74,13 @@ pub fn cdf_irwin_hall<const N: usize>(weights: &[f32; N], samples: [f32; N]) ->
// We should be able to iterate through the whole power set
// instead, and figure out K by calling count_ones(), so we can compute the result in O(2^N)
// iterations.
let x: f32 = weights
let x: f64 = weights
.iter()
.zip(samples.iter())
.map(|(weight, sample)| weight * sample)
.map(|(&weight, &sample)| weight as f64 * sample as f64)
.sum();
let mut y = 0.0f32;
let mut y = 0.0f64;
for subset in 0u32..(1 << N) {
// Number of set elements
let k = subset.count_ones();
@ -89,8 +89,8 @@ pub fn cdf_irwin_hall<const N: usize>(weights: &[f32; N], samples: [f32; N]) ->
.iter()
.enumerate()
.filter(|(i, _)| subset & (1 << i) as u32 != 0)
.map(|(_, k)| k)
.sum::<f32>();
.map(|(_, &k)| k as f64)
.sum::<f64>();
// Compute max(0, x - B_subset)^N
let z = (x - z).max(0.0).powi(N as i32);
// The parity of k determines whether the sum is negated.
@ -98,10 +98,10 @@ pub fn cdf_irwin_hall<const N: usize>(weights: &[f32; N], samples: [f32; N]) ->
}
// Divide by the product of the weights.
y /= weights.iter().product::<f32>();
y /= weights.iter().map(|&k| k as f64).product::<f64>();
// Remember to multiply by 1 / N! at the end.
y / (1..(N as i32) + 1).product::<i32>() as f32
(y / (1..(N as i32) + 1).product::<i32>() as f64) as f32
}
/// First component of each element of the vector is the computed CDF of the noise function at this
@ -148,7 +148,7 @@ pub fn vec2_as_uniform_idx(idx: Vec2<i32>) -> usize {
/// vector returned by uniform_noise, and (for convenience) the float-translated version of those
/// coordinates.
/// f should return a value with no NaNs. If there is a NaN, it will panic. There are no other
/// conditions on f. If f returns None, the value will be set to 0.0, and will be ignored for the
/// conditions on f. If f returns None, the value will be set to NaN, and will be ignored for the
/// purposes of computing the uniform range.
///
/// Returns a vec of (f32, f32) pairs consisting of the percentage of chunks with a value lower than
@ -179,7 +179,7 @@ pub fn uniform_noise<F: Float + Send>(
// position of the noise in the sorted vector (divided by the vector length).
// This guarantees a uniform distribution among the samples (excluding those that returned
// None, which will remain at zero).
let mut uniform_noise = vec![(0.0, F::zero()); WORLD_SIZE.x * WORLD_SIZE.y].into_boxed_slice();
let mut uniform_noise = vec![(0.0, F::nan()/*zero()*/); WORLD_SIZE.x * WORLD_SIZE.y].into_boxed_slice();
// NOTE: Consider using try_into here and elsewhere in this function, since i32::MAX
// technically doesn't fit in an f32 (even if we should never reach that limit).
let total = noise.len() as f32;