WIP fixes: more pleasing uplift generation.

Uses the exponential distribution now.
This commit is contained in:
Joshua Yanovski 2019-11-11 18:14:58 +01:00
parent 1b864887e7
commit 825ba1b4f4
8 changed files with 452 additions and 132 deletions

1
Cargo.lock generated
View File

@ -3305,6 +3305,7 @@ dependencies = [
"noise 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
"num 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"ordered-float 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"pretty_env_logger 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
"rand_chacha 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"rayon 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",

View File

@ -21,6 +21,7 @@ rayon = "1.2.0"
roots = "0.0.5"
serde = "1.0.102"
ron = "0.5.1"
pretty_env_logger = "0.3.0"
[dev-dependencies]
minifb = { git = "https://github.com/emoon/rust_minifb.git" }

View File

@ -1,3 +1,5 @@
use common::{terrain::TerrainChunkSize, vol::RectVolSize};
use std::f32;
use vek::*;
use veloren_world::{
sim::{RiverKind, WORLD_SIZE},
@ -8,6 +10,8 @@ const W: usize = 1024;
const H: usize = 1024;
fn main() {
pretty_env_logger::init();
let world = World::generate(1337);
let sampler = world.sim();
@ -16,12 +20,15 @@ fn main() {
minifb::Window::new("World Viewer", W, H, minifb::WindowOptions::default()).unwrap();
let mut focus = Vec2::zero();
let mut _gain = 1.0;
let mut gain = CONFIG.mountain_scale;
let mut scale = (WORLD_SIZE.x / W) as i32;
let light_direction = Vec3::new(-0.8, -1.0, 0.3).normalized();
let light_res = 3;
while win.is_open() {
let mut buf = vec![0; W * H];
const QUADRANTS : usize = 4;
const QUADRANTS: usize = 4;
let mut quads = [[0u32; QUADRANTS]; QUADRANTS];
let mut rivers = 0u32;
let mut lakes = 0u32;
@ -30,39 +37,86 @@ fn main() {
for i in 0..W {
for j in 0..H {
let pos = focus + Vec2::new(i as i32, j as i32) * scale;
/* let top_left = pos;
let top_right = focus + Vec2::new(i as i32 + light_res, j as i32) * scale;
let bottom_left = focus + Vec2::new(i as i32, j as i32 + light_res) * scale; */
let (alt, water_alt, humidity, temperature, river_kind) = sampler
let (alt, water_alt, humidity, temperature, downhill, river_kind) = sampler
.get(pos)
.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
.map(|sample| {
(
sample.alt,
sample.water_alt,
sample.humidity,
sample.temp,
sample.downhill,
sample.river.river_kind,
)
})
.unwrap_or((CONFIG.sea_level, CONFIG.sea_level, 0.0, 0.0, None, None));
let humidity = humidity.min(1.0).max(0.0);
let temperature = temperature.min(1.0).max(-1.0) * 0.5 + 0.5;
let downhill_pos = (downhill
.map(|downhill_pos| downhill_pos.map2(TerrainChunkSize::RECT_SIZE, |e, sz: u32| e / sz as i32))
.unwrap_or(pos)
- pos)/* * scale*/
+ pos;
let downhill_alt = sampler
.get(downhill_pos)
.map(|s| s.alt)
.unwrap_or(CONFIG.sea_level);
/* let alt_tl = sampler.get(top_left).map(|s| s.alt)
.unwrap_or(CONFIG.sea_level);
let alt_tr = sampler.get(top_right).map(|s| s.alt)
.unwrap_or(CONFIG.sea_level);
let alt_bl = sampler.get(bottom_left).map(|s| s.alt)
.unwrap_or(CONFIG.sea_level); */
let cross_pos = pos
+ ((downhill_pos - pos)
.map(|e| e as f32)
.rotated_z(f32::consts::FRAC_PI_2)
.map(|e| e as i32));
let cross_alt = sampler
.get(cross_pos)
.map(|s| s.alt)
.unwrap_or(CONFIG.sea_level);
let forward_vec = Vec3::new(
(downhill_pos.x - pos.x) as f64,
(downhill_pos.y - pos.y) as f64,
(downhill_alt - alt) as f64,
);
let up_vec = Vec3::new(
(cross_pos.x - pos.x) as f64,
(cross_pos.y - pos.y) as f64,
(cross_alt - alt) as f64,
);
let surface_normal = forward_vec.cross(up_vec).normalized();
// let surface_normal = Vec3::new((alt_tl - alt_tr) as f64, 1.0, (alt_tl - alt_bl) as f64).normalized();
let light = (surface_normal.dot(light_direction) + 1.0) / 2.0;
let light = (light * 0.8) + 0.2;
let water_alt = ((alt.max(water_alt) - CONFIG.sea_level) as f64 / gain as f64)
.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)
let alt = ((alt - CONFIG.sea_level) as f64 / gain as f64)
.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);
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 => {},
}
None => {}
}
buf[j * W + i] = match river_kind {
@ -79,30 +133,56 @@ fn main() {
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,
]),
None => {
let (r, g, b) = (
(alt * humidity/*alt*/ as f64).sqrt(),
0.2 + (alt * 0.8),
(alt * temperature/*alt*/ as f64).sqrt(),
);
u32::from_le_bytes([
(b * light * 255.0) as u8,
(g * light * 255.0) as u8,
(r * light * 255.0) as u8,
255,
])
/* 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,
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>()
quads.iter().map(|x| x.iter().sum::<u32>()).sum::<u32>()
);
}
if win.get_mouse_down(minifb::MouseButton::Left) {
if let Some((mx, my)) = win.get_mouse_pos(minifb::MouseMode::Clamp) {
let pos = focus + Vec2::new(mx as i32, my as i32) * scale;
println!(
"Chunk position: {:?}",
pos.map2(TerrainChunkSize::RECT_SIZE, |e, f| e * f as i32)
);
}
}
if win.is_key_down(minifb::Key::W) {
focus.y -= spd * scale;
}
@ -116,10 +196,10 @@ fn main() {
focus.x += spd * scale;
}
if win.is_key_down(minifb::Key::Q) {
_gain += 10.0;
gain += 64.0;
}
if win.is_key_down(minifb::Key::E) {
_gain -= 10.0;
gain = (gain - 64.0).max(64.0);
}
if win.is_key_down(minifb::Key::R) {
scale += 1;

View File

@ -168,7 +168,7 @@ impl<'a> BlockGen<'a> {
temp,
humidity,
chunk,
stone_col,
stone_col,
..
} = sample;
@ -253,7 +253,6 @@ impl<'a> BlockGen<'a> {
saturate_srgb(sub_surface_color, 0.45).map(|e| (e * 255.0) as u8),
stone_col,
(height - grass_depth - wposf.z as f32) * 0.15,
);
// Underground

View File

@ -232,8 +232,6 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
let rockiness = sim.get_interpolated(wpos, |chunk| chunk.rockiness)?;
let tree_density = sim.get_interpolated(wpos, |chunk| chunk.tree_density)?;
let spawn_rate = sim.get_interpolated(wpos, |chunk| chunk.spawn_rate)?;
let alt = sim.get_interpolated_monotone(wpos, |chunk| chunk.alt)?;
let sim_chunk = sim.get(chunk_pos)?;
let neighbor_coef = TerrainChunkSize::RECT_SIZE.map(|e| e as f64);
let my_chunk_idx = vec2_as_uniform_idx(chunk_pos);
@ -408,6 +406,41 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
)
});
let downhill = sim_chunk.downhill;
let downhill_pos = downhill.and_then(|downhill_pos| sim.get(downhill_pos));
debug_assert!(sim_chunk.water_alt >= CONFIG.sea_level);
let downhill_water_alt = downhill_pos
.map(|downhill_chunk| {
downhill_chunk
.water_alt
.min(sim_chunk.water_alt)
.max(sim_chunk.alt.min(sim_chunk.water_alt))
})
.unwrap_or(CONFIG.sea_level);
let is_cliffs = sim_chunk.is_cliffs;
let near_cliffs = sim_chunk.near_cliffs;
let is_rocky = sim_chunk.humidity < CONFIG.desert_hum
&& (/*sim_chunk.temp < CONFIG.snow_temp || */sim_chunk.alt.sub(CONFIG.sea_level) >= CONFIG.mountain_scale * 0.25);
/* let downhill_alt_rocky = downhill_pos
.map(|downhill_chunk| {
downhill_chunk.humidity < CONFIG.forest_hum &&
(downhill_chunk.temperature < CONFIG.|| downhill_chunk.alt.sub(CONFIG.sea_level) >= CONFIG.mountain_scale * 0.25)
})
.unwrap_or(CONFIG.sea_level); */
let alt = if
/*humidity < CONFIG.desert_hum &&
(temp < CONFIG.snow_temp ||
downhill_alt.sub(CONFIG.sea_level) >= CONFIG.mountain_scale * 0.25)*/
is_rocky {
sim.get_interpolated_bilinear(wpos, |chunk| chunk.alt)?
} else {
sim.get_interpolated_monotone(wpos, |chunk| chunk.alt)?
};
// Find the average distance to each neighboring body of water.
let mut river_count = 0.0f64;
let mut overlap_count = 0.0f64;
@ -542,13 +575,6 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
}
};
let alt_for_river = alt
+ if overlap_count == 0.0 {
0.0
} else {
river_overlap_distance_product / overlap_count
} as f32;
let cliff_hill = (sim
.gen_ctx
.small_nz
@ -558,34 +584,26 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
let riverless_alt_delta = (sim
.gen_ctx
.small_nz
.get((wposf_turb.div(200.0)).into_array()) as f32)
.get((wposf_turb.div(/*200.0*//*50.0*//*24.0*//*56.0 / (chaos as f64).max(0.05)*/50.0)).into_array()) as f32)
.abs()
.mul(chaos.max(0.05))
.mul(27.0)
+ (sim
.mul(3.0)
/* .mul(chaos.max(0.05))
.mul(27.0) */
/* + (sim
.gen_ctx
.small_nz
.get((wposf_turb.div(400.0)).into_array()) as f32)
.abs()
.mul((1.0 - chaos).max(0.3))
.mul(1.0 - humidity)
.mul(32.0);
.mul(32.0) */;
let downhill = sim_chunk.downhill;
let downhill_pos = downhill.and_then(|downhill_pos| sim.get(downhill_pos));
debug_assert!(sim_chunk.water_alt >= CONFIG.sea_level);
let downhill_water_alt = downhill_pos
.map(|downhill_chunk| {
downhill_chunk
.water_alt
.min(sim_chunk.water_alt)
.max(sim_chunk.alt.min(sim_chunk.water_alt))
})
.unwrap_or(CONFIG.sea_level);
let is_cliffs = sim_chunk.is_cliffs;
let near_cliffs = sim_chunk.near_cliffs;
let alt_for_river = alt
+ if overlap_count == 0.0 {
0.0
} else {
river_overlap_distance_product / overlap_count
} as f32;
let river_gouge = 0.5;
let (in_water, alt_, water_level, warp_factor) = if let Some((
@ -848,9 +866,9 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
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(
// 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,
@ -896,10 +914,11 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
sand,
temp.sub(CONFIG.snow_temp)
.div(CONFIG.desert_temp.sub(CONFIG.snow_temp))
.mul(/*4.5*/0.5),
.mul(/*4.5*/ 0.5),
),
cliff,
alt.sub(CONFIG.mountain_scale * 0.25)
alt.sub(CONFIG.sea_level)
.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,
@ -918,7 +937,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
/*.sub((marble - 0.5) * 0.05)
.mul(256.0)*/
.mul(1.0),
// .mul(2.0),
// .mul(2.0),
),
// 0 to tropical_temp
grass,
@ -929,20 +948,20 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
temp.sub(CONFIG.tropical_temp)
.div(CONFIG.desert_temp.sub(CONFIG.tropical_temp))
.mul(1.0),
// .mul(2.0),
// .mul(2.0),
),
// above desert_temp
sand,
temp.sub(CONFIG.desert_temp)
.div(1.0 - CONFIG.desert_temp)
.mul(4.0),
// .mul(2.0),
// .mul(2.0),
),
humidity
.sub(CONFIG.desert_hum)
.div(CONFIG.forest_hum.sub(CONFIG.desert_hum))
.mul(1.0),
// .mul(2.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.
@ -961,20 +980,20 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
temp.sub(CONFIG.tropical_temp)
.div(CONFIG.desert_temp.sub(CONFIG.tropical_temp))
.mul(1.0),
// .mul(2.0),
// .mul(2.0),
),
// above desert_temp
sand,
temp.sub(CONFIG.desert_temp)
.div(1.0 - CONFIG.desert_temp)
.mul(4.0),
// .mul(2.0),
// .mul(2.0),
),
humidity
.sub(CONFIG.forest_hum)
.div(CONFIG.jungle_hum.sub(CONFIG.forest_hum))
.mul(1.0),
// .mul(2.0),
// .mul(2.0),
);
// From jungle humidity upwards, we go from snow to grass to rainforest to tropics to sand.
let ground = Rgb::lerp(
@ -992,14 +1011,14 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
temp.sub(CONFIG.tropical_temp)
.div(CONFIG.desert_temp.sub(CONFIG.tropical_temp))
.mul(4.0),
// .mul(2.0),
// .mul(2.0),
),
// above desert_temp
sand,
temp.sub(CONFIG.desert_temp)
.div(1.0 - CONFIG.desert_temp)
.mul(4.0),
// .mul(2.0),
// .mul(2.0),
),
humidity.sub(CONFIG.jungle_hum).mul(1.0),
);

View File

@ -485,7 +485,11 @@ fn get_max_slope(h: &[f32], rock_strength_nz: &(impl NoiseFn<Point3<f64>> + Sync
// Normalized to be between 6 and and 54 degrees.
let div_factor = 32.0;
let rock_strength = rock_strength_nz
.get([wposf.x / div_factor, wposf.y / div_factor, wposz])
.get([
wposf.x, /* / div_factor*/
wposf.y, /* / div_factor*/
wposz,
])
.max(-1.0)
.min(1.0)
* 0.5
@ -581,6 +585,9 @@ fn erode(
) {
log::debug!("Done draining...");
let mmaxh = 1.0;
// Landslide constant: ideally scaled to 10 m^-2 / y^-1
let l = 200.0 * max_uplift as f64;
// Erosion constant.
let k = erosion_base as f64 + 2.244 / mmaxh as f64 * max_uplift as f64;
let ((dh, indirection, newh, area), max_slope) = rayon::join(
|| {
@ -640,8 +647,8 @@ fn erode(
let is_lake_bottom = indirection_idx < 0;
// Test the slope.
let max_slope = max_slope[posi] as f64;
let dz = (new_h_i - h_j) * CONFIG.mountain_scale as f64;
let mag_slope = dz.abs() / neighbor_distance;
let dz = (new_h_i - h_j).max(0.0) * CONFIG.mountain_scale as f64;
let mag_slope = dz/*.abs()*/ / neighbor_distance;
let _fake_neighbor = is_lake_bottom && dxy.x.abs() > 1.0 && dxy.y.abs() > 1.0;
// If you're on the lake bottom and not right next to your neighbor, don't compute a
// slope.
@ -656,9 +663,14 @@ fn erode(
// exactly max_slope.
// max_slope = (old_h_i + dh - h_j) * CONFIG.mountain_scale / NEIGHBOR_DISTANCE
// dh = max_slope * NEIGHBOR_DISTANCE / CONFIG.mountain_scale + h_j - old_h_i.
let slope = dz.signum() * max_slope;
sums += max_slope;
new_h_i = slope * neighbor_distance / CONFIG.mountain_scale as f64 + h_j;
let dh = max_slope * neighbor_distance / CONFIG.mountain_scale as f64;
new_h_i = (h_j + dh).max(new_h_i - l * (mag_slope - max_slope));
let dz = (new_h_i - h_j).max(0.0) * CONFIG.mountain_scale as f64;
let slope = dz/*.abs()*/ / neighbor_distance;
sums += slope;
// let slope = dz.signum() * max_slope;
// new_h_i = slope * neighbor_distance / CONFIG.mountain_scale as f64 + h_j;
// sums += max_slope;
} else {
sums += mag_slope;
// Just use the computed rate.

View File

@ -47,7 +47,10 @@ use vek::*;
// cleanly representable in f32 (that stops around 1024 * 4 * 1024 * 4, for signed floats anyway)
// but I think that is probably less important since I don't think we actually cast a chunk id to
// float, just coordinates... could be wrong though!
pub const WORLD_SIZE: Vec2<usize> = Vec2 { x: 1024, y: 1024 };
pub const WORLD_SIZE: Vec2<usize> = Vec2 {
x: 1024 * 2,
y: 1024 * 2,
};
/// A structure that holds cached noise values and cumulative distribution functions for the input
/// that led to those values. See the definition of InverseCdf for a description of how to
@ -67,7 +70,7 @@ struct GenCdf {
rivers: Box<[RiverData]>,
}
pub(crate) struct GenCtx {
pub(crate) struct GenCtx {
pub turb_x_nz: SuperSimplex,
pub turb_y_nz: SuperSimplex,
pub chaos_nz: RidgedMulti,
@ -108,26 +111,43 @@ pub struct WorldSim {
impl WorldSim {
pub fn generate(seed: u32) -> Self {
let mut rng = ChaChaRng::from_seed(seed_expan::rng_state(seed));
let continent_scale = 5_000.0/*32768.0*/;
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(7).set_seed(rng.gen()),
chaos_nz: RidgedMulti::new()
.set_octaves(/*7*//*3*/ 7)
.set_frequency(
/*RidgedMulti::DEFAULT_FREQUENCY **/ 3_000.0 * 8.0 / continent_scale,
)
.set_seed(rng.gen()),
hill_nz: SuperSimplex::new().set_seed(rng.gen()),
alt_nz: HybridMulti::new()
.set_octaves(8)
.set_persistence(0.1)
.set_octaves(/*3*//*2*/ 8)
// 1/2048*32*1024 = 16
.set_frequency(
/*HybridMulti::DEFAULT_FREQUENCY*/
(10_000.0/* * 2.0*/ / continent_scale) as f64,
)
// .set_frequency(1.0 / ((1 << 0) as f64))
// .set_lacunarity(1.0)
.set_persistence(/*0.5*//*0.5*/ 0.5)
.set_seed(rng.gen()),
//temp_nz: SuperSimplex::new().set_seed(rng.gen()),
temp_nz: Fbm::new()
.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()),
temp_nz: Fbm::new()
.set_octaves(6)
.set_persistence(0.5)
// 1/2^14*1024*32 = 2
// 1/(2^14-2^12)*1024*32 = 8/3 ~= 3
.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(2).set_seed(rng.gen()),
rock_nz: HybridMulti::new().set_persistence(0.3).set_seed(rng.gen()),
@ -160,9 +180,16 @@ impl WorldSim {
let river_seed = RandomField::new(rng.gen());
let rock_strength_nz = Fbm::new()
.set_octaves(8)
.set_persistence(0.9)
.set_persistence(/*0.9*/ 2.0)
.set_frequency(/*0.9*/ Fbm::DEFAULT_FREQUENCY / (64.0 * 32.0))
.set_seed(rng.gen());
let max_erosion_per_delta_t = 32.0 / CONFIG.mountain_scale as f64;
let erosion_pow_low = /*0.25*//*1.5*//*2.0*//*0.5*//*4.0*//*0.25*//*1.0*//*2.0*//*1.5*//*1.5*//*0.35*//*0.43*//*0.5*//*0.45*//*0.37*/1.002;
let erosion_pow_high = /*1.5*//*1.0*//*0.55*//*0.51*//*2.0*/1.002;
let erosion_center = /*0.45*//*0.75*//*0.75*//*0.5*//*0.75*/0.5;
let n_steps = 150; //150;//200;
// No NaNs in these uniform vectors, since the original noise value always returns Some.
let ((alt_base, _), (chaos, _)) = rayon::join(
|| {
@ -177,15 +204,17 @@ impl WorldSim {
.min(1.0)
.max(-1.0))
.sub(0.05)
.mul(0.35),
.mul(0.35), /*-0.0175*/
)
})
},
|| {
uniform_noise(|_, wposf| {
// From 0 to 1.6, but the distribution before the max is from -1 and 1, so there is
// a 50% chance that hill will end up at 0.
let hill = (0.0
// From 0 to 1.6, but the distribution before the max is from -1 and 1.6, so there is
// a 50% chance that hill will end up at 0.3 or lower, and probably a very high
// change it will be exactly 0.
let hill = (0.0f64
//.add(0.0)
+ gen_ctx
.hill_nz
.get((wposf.div(1_500.0)).into_array())
@ -213,6 +242,7 @@ impl WorldSim {
.add(1.0)
.mul(0.5)
// [0, 1] * [0.4, 1] = [0, 1] (but probably towards the lower end)
//.mul(1.0)
.mul(
(gen_ctx
.chaos_nz
@ -224,7 +254,7 @@ impl WorldSim {
.min(1.0),
)
// Chaos is always increased by a little when we're on a hill (but remember
// that hill is 0 about 50% of the time).
// that hill is 0.3 or less about 50% of the time).
// [0, 1] + 0.15 * [0, 1.6] = [0, 1.24]
.add(0.2 * hill)
// We can't have *no* chaos!
@ -239,7 +269,7 @@ impl WorldSim {
// 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| {
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.
//
@ -272,7 +302,7 @@ impl WorldSim {
x.abs().powf(pow) * x.signum()
}
(0.0 + alt_main
(0.0 + alt_main/*0.4*/
+ (gen_ctx
.small_nz
.get((wposf.div(300.0)).into_array())
@ -282,6 +312,7 @@ impl WorldSim {
.mul(0.3)
.add(1.0)
.mul(0.4)
/*0.52*/
+ spring(alt_main.abs().powf(0.5).min(0.75).mul(60.0).sin(), 4.0).mul(0.045))
};
@ -296,7 +327,24 @@ 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)/*0.25*/))
((alt_base[posi].1
+ alt_main /*1.0*/
.mul(
(chaos[posi].1 as f64) /*.mul(2.0).sub(1.0).max(0.0)*/
.powf(1.2), /*0.25)*//*0.285*/
)/*0.1425*/)
.mul(map_edge_factor(posi) as f64)
.add(
(CONFIG.sea_level as f64)
.div(CONFIG.mountain_scale as f64)
.mul(map_edge_factor(posi) as f64),
)
.sub((CONFIG.sea_level as f64).div(CONFIG.mountain_scale as f64)))
as f32,
)
/* Some(
// FIXME: May fail on big-endian platforms.
((alt_base[posi].1 as f64 + 0.5 + (/*alt_main./*to_le_bytes()[7]*/to_bits() & 1) as f64 * ((1.0 / CONFIG.mountain_scale as f64).powf(1.0 / erosion_pow_low)) + */alt_main / CONFIG.mountain_scale as f64 * 128.0).mul(0.1).powf(1.2))
.mul(map_edge_factor(posi) as f64)
.add(
(CONFIG.sea_level as f64)
@ -305,7 +353,7 @@ impl WorldSim {
)
.sub((CONFIG.sea_level as f64).div(CONFIG.mountain_scale as f64)))
as f32,
)
) */
});
// Calculate oceans.
@ -313,20 +361,28 @@ impl WorldSim {
let is_ocean = get_oceans(old_height);
let is_ocean_fn = |posi: usize| is_ocean[posi];
/* // Recalculate altitudes without oceans.
// 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, _| {
let (alt_old_no_ocean, alt_old_inverse) = uniform_noise(|posi, _| {
if is_ocean_fn(posi) {
None
} else {
Some(old_height(posi)/*.abs()*/)
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.
let alt_old_max_uniform = 1.0;
let alt_old_center_uniform = erosion_center;
let (_alt_old_min_index, alt_old_min) = alt_old_inverse.first().unwrap();
let (_alt_old_max_index, alt_old_max) = alt_old_inverse.last().unwrap();
let (_alt_old_mid_index, alt_old_mid) =
alt_old_inverse[(alt_old_inverse.len() as f64 * erosion_center) as usize];
let alt_old_center =
((alt_old_mid - alt_old_min) as f64 / (alt_old_max - alt_old_min) as f64);
/* // 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;
@ -337,21 +393,52 @@ impl WorldSim {
.unwrap();
let &(alt_old_max_index, _alt_old_max) = alt_old_inverse.last().unwrap();
let alt_old_min_uniform = alt_old[alt_old_min_index].0;
let alt_old_max_uniform = alt_old[alt_old_max_index].0;
let alt_old_max_uniform = alt_old[alt_old_max_index].0; */
// Perform some erosion.
let max_erosion_per_delta_t = 32.0 / CONFIG.mountain_scale as f64;
// Logistic regression. Make sure x ∈ (0, 1).
let logit = |x: f64| x.ln() - (-x).ln_1p();
// 0.5 + 0.5 * tanh(ln(1 / (1 - 0.1) - 1) / (2 * (sqrt(3)/pi)))
let logistic_2_base = 3.0f64.sqrt() * f64::consts::FRAC_2_PI;
let logistic_base = /*3.0f64.sqrt() * f64::consts::FRAC_1_PI*/1.0f64;
// Assumes μ = 0, σ = 1
let logistic_cdf = |x: f64| (x / logistic_2_base).tanh() * 0.5 + 0.5;
let erosion_pow = 2.0;
let n_steps = 100;
let erosion_factor = |x: f64| logistic_cdf(erosion_pow * logit(x));
let exp_inverse_cdf = |x: f64/*, pow: f64*/| -(-x).ln_1p()/* / ln(pow)*/;
// 2^((2^10-2)/256) = 15.91...
// -ln(1-(1-(2^(-22)*0.5)))
// -ln(1-(1-(2^(-53)*0.5)))
// ((-ln(1-((1-2^(-53)*0.5))))/ln(e))/((-ln(1-((2^(-53)*0.5))))/ln(e))
// ((-ln(1-((0.5))))/ln(2))/((-ln(1-((1 - 2^(-53)*0.5))))/ln(2))
// ((-ln(1-((0.5))))/ln(e))/((-ln(1-((1 - 2^(-53)*0.5))))/ln(e))
// ((-ln(1-((0.5))))/ln(e))/((-ln(1-((2^(-53)*0.5))))/ln(e))
// ((-ln(1-((1-2^(-53)))))/ln(1.002))/((-ln(1-((1 - 2^(-53)*0.5))))/ln(1+2^(-10*2)*0.5))
// ((-ln(1-((0.9999999999999999))))/ln(e))/((-ln(1-((1 - 2^(-53)*0.5))))/ln(1+2^(-53)*0.5))
//
// ((-ln(1-((1-2^(-10*2)))))/ln(1.002))/((-ln(1-((1 - 2^(-10*2)))))/ln(1+2^(-9)))
// ((-ln(1-((2^(-10*2)))))/ln(1.002))/((-ln(1-((1 - 2^(-10*2)))))/ln(1+2^(-9)))
// ((-ln(1-((1-2^(-10*2)))))/ln(1.002))/((-ln(1-((1 - 2^(-10*2)))))/ln(1.002))
let min_epsilon =
1.0 / (WORLD_SIZE.x as f64 * WORLD_SIZE.y as f64).max(f64::EPSILON as f64 * 0.5);
let max_epsilon = (1.0 - 1.0 / (WORLD_SIZE.x as f64 * WORLD_SIZE.y as f64))
.min(1.0 - f64::EPSILON as f64 * 0.5);
let alt_exp_min_uniform = exp_inverse_cdf(min_epsilon);
let alt_exp_max_uniform = exp_inverse_cdf(max_epsilon);
// let erosion_pow = 2.0;
// let n_steps = 100;//150;
// let erosion_factor = |x: f64| logistic_cdf(erosion_pow * logit(x));
let log_odds = |x: f64| {
logit(x)
- logit(
/*erosion_center*/ alt_old_center_uniform, /*alt_old_center*/
)
};
/* let erosion_factor = |x: f64| logistic_cdf(logistic_base * if x <= /*erosion_center*/alt_old_center_uniform/*alt_old_center*/ { erosion_pow_low.ln() } else { erosion_pow_high.ln() } * log_odds(x))/*0.5 + (x - 0.5).signum() * ((x - 0.5).mul(2.0).abs(
).powf(erosion_pow).mul(0.5))*/; */
let erosion_factor = |x: f64| (/*if x <= /*erosion_center*/alt_old_center_uniform/*alt_old_center*/ { erosion_pow_low.ln() } else { erosion_pow_high.ln() } * */(exp_inverse_cdf(x) - alt_exp_min_uniform) / (alt_exp_max_uniform - alt_exp_min_uniform))/*0.5 + (x - 0.5).signum() * ((x - 0.5).mul(2.0).abs(
).powf(erosion_pow).mul(0.5))*/;
let alt = do_erosion(
0.0,
max_erosion_per_delta_t as f32,
@ -362,22 +449,141 @@ impl WorldSim {
if is_ocean_fn(posi) {
old_height(posi)
} else {
5.0 / CONFIG.mountain_scale
let wposf = (uniform_idx_as_vec2(posi)
* TerrainChunkSize::RECT_SIZE.map(|e| e as i32))
.map(|e| e as f64);
let alt_main = {
// Extension upwards from the base. A positive number from 0 to 1 curved to be
// maximal at 0. Also to be multiplied by CONFIG.mountain_scale.
let alt_main = (gen_ctx
.alt_nz
.get((wposf.div(2_000.0)).into_array())
.min(1.0)
.max(-1.0))
.abs()
.powf(1.35);
fn spring(x: f64, pow: f64) -> f64 {
x.abs().powf(pow) * x.signum()
}
(0.0 + alt_main
+ (gen_ctx
.small_nz
.get((wposf.div(300.0)).into_array())
.min(1.0)
.max(-1.0))
.mul(alt_main.powf(0.8).max(/*0.25*/ 0.15))
.mul(0.3)
.add(1.0)
.mul(0.4)
+ spring(alt_main.abs().powf(0.5).min(0.75).mul(60.0).sin(), 4.0)
.mul(0.045))
};
// old_height_uniform(posi) *
(/*((old_height(posi) - alt_old_min) as f64 / (alt_old_max - alt_old_min) as f64) **/(((6.0 / 360.0 * 2.0 * f64::consts::PI).tan()
* TerrainChunkSize::RECT_SIZE.reduce_partial_min() as f64)
.floor()
/ CONFIG.mountain_scale as f64)) as f32
// 5.0 / CONFIG.mountain_scale
}
},
is_ocean_fn,
|posi| {
let height = ((old_height_uniform(posi) - alt_old_min_uniform) as f64
if is_ocean_fn(posi) {
return 0.0;
}
let wposf = (uniform_idx_as_vec2(posi)
* TerrainChunkSize::RECT_SIZE.map(|e| e as i32))
.map(|e| e as f64);
let alt_main = {
// Extension upwards from the base. A positive number from 0 to 1 curved to be
// maximal at 0. Also to be multiplied by CONFIG.mountain_scale.
let alt_main = (gen_ctx
.alt_nz
.get((wposf.div(2_000.0)).into_array())
.min(1.0)
.max(-1.0))
.abs()
.powf(1.35);
fn spring(x: f64, pow: f64) -> f64 {
x.abs().powf(pow) * x.signum()
}
(0.0 + alt_main
+ (gen_ctx
.small_nz
.get((wposf.div(300.0)).into_array())
.min(1.0)
.max(-1.0))
.mul(alt_main.powf(0.8).max(/*0.25*/ 0.15))
.mul(0.3)
.add(1.0)
.mul(0.4)
+ spring(alt_main.abs().powf(0.5).min(0.75).mul(60.0).sin(), 4.0)
.mul(0.045))
};
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);
/*((old_height(posi) - alt_old_min) as f64
/ (alt_old_max - alt_old_min) as f64)*/
;
let height = height.mul(max_epsilon - min_epsilon).add(min_epsilon);
/*.max(1e-7 / CONFIG.mountain_scale as f64)
.min(1.0f64 - 1e-7);*/
/* let alt_main = {
// Extension upwards from the base. A positive number from 0 to 1 curved to be
// maximal at 0. Also to be multiplied by CONFIG.mountain_scale.
let alt_main = (gen_ctx
.alt_nz
.get((wposf.div(2_000.0)).into_array())
.min(1.0)
.max(-1.0))
.abs()
.powf(1.35);
fn spring(x: f64, pow: f64) -> f64 {
x.abs().powf(pow) * x.signum()
}
(0.0 + alt_main
+ (gen_ctx
.small_nz
.get((wposf.div(300.0)).into_array())
.min(1.0)
.max(-1.0))
.mul(alt_main.powf(0.8).max(/*0.25*/ 0.15))
.mul(0.3)
.add(1.0)
.mul(0.4)
+ spring(alt_main.abs().powf(0.5).min(0.75).mul(60.0).sin(), 4.0).mul(0.045))
}; */
// let height = height + (alt_main./*to_le_bytes()[7]*/to_bits() & 1) as f64 * ((1.0 / CONFIG.mountain_scale as f64).powf(1.0 / erosion_pow_low));
let height = erosion_factor(height);
assert!(height >= 0.0);
assert!(height <= 1.0);
// assert!(alt_main >= 0.0);
let (bump_factor, bump_max) = if
/*height < f32::EPSILON as f64 * 0.5/*false*/*/
false {
(
/*(alt_main./*to_le_bytes()[7]*/to_bits() & 1) as f64*/
(alt_main / CONFIG.mountain_scale as f64 * 128.0).mul(0.1).powf(1.2) * /*(1.0 / CONFIG.mountain_scale as f64)*/(f32::EPSILON * 0.5) as f64,
(f32::EPSILON * 0.5) as f64,
)
} else {
(0.0, 0.0)
};
let height = height
.mul(max_erosion_per_delta_t * 7.0 / 8.0)
.add(max_erosion_per_delta_t / 8.0);
.add(max_erosion_per_delta_t / 8.0)
.sub(/*1.0 / CONFIG.mountain_scale as f64*/ bump_max)
.add(bump_factor);
/* .sub(/*1.0 / CONFIG.mountain_scale as f64*/(f32::EPSILON * 0.5) as f64)
.add(bump_factor); */
height as f32
},
);
@ -530,7 +736,8 @@ 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)
}
})
},
@ -1044,7 +1251,7 @@ impl SimChunk {
// 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 (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 (humid_uniform, _) = gen_cdf.humid_base[posi];
@ -1074,7 +1281,7 @@ impl SimChunk {
// 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];
const TEMP_WEIGHTS: [f32; 2] = [/*1.5, */ 1.0, 2.0];
let temp = /*if flux_non_uniform.is_nan() {
0.0
} else */{

View File

@ -179,7 +179,8 @@ 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::nan()/*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;