From 825ba1b4f4d422bcefe8fa75305ed210d816a5f6 Mon Sep 17 00:00:00 2001 From: Joshua Yanovski Date: Mon, 11 Nov 2019 18:14:58 +0100 Subject: [PATCH] WIP fixes: more pleasing uplift generation. Uses the exponential distribution now. --- Cargo.lock | 1 + world/Cargo.toml | 1 + world/examples/water.rs | 152 +++++++++++++++----- world/src/block/mod.rs | 3 +- world/src/column/mod.rs | 105 ++++++++------ world/src/sim/erosion.rs | 24 +++- world/src/sim/mod.rs | 295 +++++++++++++++++++++++++++++++++------ world/src/sim/util.rs | 3 +- 8 files changed, 452 insertions(+), 132 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 434a8f7f4b..bfd87ed615 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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)", diff --git a/world/Cargo.toml b/world/Cargo.toml index dc287a1a18..f9e9a70756 100644 --- a/world/Cargo.toml +++ b/world/Cargo.toml @@ -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" } diff --git a/world/examples/water.rs b/world/examples/water.rs index a6ae8266d3..92ba1e3715 100644 --- a/world/examples/water.rs +++ b/world/examples/water.rs @@ -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::() ).sum::() + quads.iter().map(|x| x.iter().sum::()).sum::() ); } + 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; diff --git a/world/src/block/mod.rs b/world/src/block/mod.rs index cbbb9749df..17d22bf217 100644 --- a/world/src/block/mod.rs +++ b/world/src/block/mod.rs @@ -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 diff --git a/world/src/column/mod.rs b/world/src/column/mod.rs index d389199b84..5732e57519 100644 --- a/world/src/column/mod.rs +++ b/world/src/column/mod.rs @@ -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), ); diff --git a/world/src/sim/erosion.rs b/world/src/sim/erosion.rs index b4af42878e..672ebadbe8 100644 --- a/world/src/sim/erosion.rs +++ b/world/src/sim/erosion.rs @@ -485,7 +485,11 @@ fn get_max_slope(h: &[f32], rock_strength_nz: &(impl NoiseFn> + 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. diff --git a/world/src/sim/mod.rs b/world/src/sim/mod.rs index 8cf43400b2..67fb439c47 100644 --- a/world/src/sim/mod.rs +++ b/world/src/sim/mod.rs @@ -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 = Vec2 { x: 1024, y: 1024 }; +pub const WORLD_SIZE: Vec2 = 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 */{ diff --git a/world/src/sim/util.rs b/world/src/sim/util.rs index d51c9d2b5b..e7fc3aa7b7 100644 --- a/world/src/sim/util.rs +++ b/world/src/sim/util.rs @@ -179,7 +179,8 @@ pub fn uniform_noise( // 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;