diff --git a/.gitattributes b/.gitattributes index d5a660767c..d6d5634e45 100644 --- a/.gitattributes +++ b/.gitattributes @@ -4,3 +4,4 @@ *.wav filter=lfs diff=lfs merge=lfs -text *.ogg filter=lfs diff=lfs merge=lfs -text *.ico filter=lfs diff=lfs merge=lfs -text +assets/world/map/*.bin filter=lfs diff=lfs merge=lfs -text diff --git a/assets/world/map/veloren_0_5_0_0.bin b/assets/world/map/veloren_0_5_0_0.bin new file mode 100644 index 0000000000..f121098936 --- /dev/null +++ b/assets/world/map/veloren_0_5_0_0.bin @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4fd18a0b50764564cfc7ffc6c2551100ca564c951fe531f508469e029d02482c +size 16777236 diff --git a/server/src/lib.rs b/server/src/lib.rs index 9011d49d34..fb492f5c0a 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -112,10 +112,15 @@ impl Server { settings.world_seed, WorldOpts { seed_elements: true, - world_file: if let Some(ref file) = settings.map_file { - FileOpts::Load(file.clone()) + world_file: if let Some(ref opts) = settings.map_file { + opts.clone() } else { - FileOpts::Generate + // Load default map from assets. + // + // TODO: Consider using some naming convention to automatically change this + // with changing versions, or at least keep it in a constant somewhere that's + // easy to change. + FileOpts::LoadAsset("world.map.veloren_0_5_0_0".into()) }, ..WorldOpts::default() }, diff --git a/server/src/settings.rs b/server/src/settings.rs index 92d6f7409b..0a8bffa4ec 100644 --- a/server/src/settings.rs +++ b/server/src/settings.rs @@ -1,6 +1,9 @@ use portpicker::pick_unused_port; use serde_derive::{Deserialize, Serialize}; use std::{fs, io::prelude::*, net::SocketAddr, path::PathBuf}; +use world::sim::FileOpts; + +const DEFAULT_WORLD_SEED: u32 = 5284; #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(default)] @@ -15,7 +18,9 @@ pub struct ServerSettings { //pub login_server: whatever pub start_time: f64, pub admins: Vec, - pub map_file: Option, + /// When set to None, loads the default map file (if available); otherwise, uses the value of + /// the file options to decide how to proceed. + pub map_file: Option, } impl Default for ServerSettings { @@ -23,7 +28,7 @@ impl Default for ServerSettings { Self { gameserver_address: SocketAddr::from(([0; 4], 14004)), metrics_address: SocketAddr::from(([0; 4], 14005)), - world_seed: 5284, + world_seed: DEFAULT_WORLD_SEED, server_name: "Veloren Alpha".to_owned(), server_description: "This is the best Veloren server.".to_owned(), max_players: 100, @@ -89,6 +94,11 @@ impl ServerSettings { } pub fn singleplayer() -> Self { + let mut load = Self::load(); + if let None = load.map_file { + // If lodaing the default map file, make sure the seed is also default. + load.world_seed = DEFAULT_WORLD_SEED; + }; Self { //BUG: theoretically another process can grab the port between here and server creation, however the timewindow is quite small gameserver_address: SocketAddr::from(( diff --git a/world/src/block/mod.rs b/world/src/block/mod.rs index 9ba202dfed..5f1b088dd2 100644 --- a/world/src/block/mod.rs +++ b/world/src/block/mod.rs @@ -70,7 +70,7 @@ impl<'a> BlockGen<'a> { // (height max) [12.70, 46.33] + 3 = [15.70, 49.33] / (1.0 + 3.0 * cliff_sample.chaos) + 3.0; - // COnservative range of radius: [8, 47] + // Conservative range of radius: [8, 47] let radius = RandomField::new(seed + 2).get(cliff_pos3d) % 48 + 8; max_height.max( @@ -191,8 +191,8 @@ impl<'a> BlockGen<'a> { .gen_ctx .warp_nz .get(wposf.div(24.0)) - .mul((chaos - 0.1).max(0.0).powf(2.0)) - .mul(48.0); + .mul((chaos - 0.1).max(0.0).min(1.0).powf(2.0)) + .mul(/*48.0*/ 16.0); let warp = Lerp::lerp(0.0, warp, warp_factor); let surface_height = alt + warp; @@ -227,7 +227,7 @@ impl<'a> BlockGen<'a> { false, height, on_cliff, - basement.min(basement + warp), + basement + height - alt, (if water_level <= alt { water_level + warp } else { @@ -430,7 +430,7 @@ impl<'a> ZCache<'a> { 0.0 }; - let min = self.sample.alt - (self.sample.chaos * 48.0 + cave_depth); + let min = self.sample.alt - (self.sample.chaos.min(1.0) * 16.0 + cave_depth); let min = min - 4.0; let cliff = BlockGen::get_cliff_height( diff --git a/world/src/column/mod.rs b/world/src/column/mod.rs index dec47fdd52..5d400490c2 100644 --- a/world/src/column/mod.rs +++ b/world/src/column/mod.rs @@ -829,12 +829,12 @@ impl<'a> Sampler<'a> for ColumnGen<'a> { /* .mul((1.0 - chaos).min(1.0).max(0.3)) .mul(1.0 - humidity) */ /* .mul(32.0) */; - let basement = alt_ - + sim./*get_interpolated*/get_interpolated_monotone(wpos, |chunk| chunk.basement.sub(chunk.alt))?; let riverless_alt_delta = Lerp::lerp(0.0, riverless_alt_delta, warp_factor); let alt = alt_ + riverless_alt_delta; - let basement = basement.min(alt); + let basement = alt + + sim./*get_interpolated*/get_interpolated_monotone(wpos, |chunk| chunk.basement.sub(chunk.alt))?; + // let basement = basement.min(alt); let rock = (sim.gen_ctx.small_nz.get( Vec3::new(wposf.x, wposf.y, alt as f64) diff --git a/world/src/lib.rs b/world/src/lib.rs index 3a7579fd47..61325b49ec 100644 --- a/world/src/lib.rs +++ b/world/src/lib.rs @@ -1,6 +1,6 @@ #![deny(unsafe_code)] #![allow(incomplete_features)] -#![feature(const_generics, label_break_value)] +#![feature(arbitrary_enum_discriminant, const_generics, label_break_value)] mod all; mod block; diff --git a/world/src/sim/erosion.rs b/world/src/sim/erosion.rs index a235472ff2..12da4b279e 100644 --- a/world/src/sim/erosion.rs +++ b/world/src/sim/erosion.rs @@ -232,7 +232,7 @@ pub fn get_rivers, G: Float + Into>( let mut rivers = vec![RiverData::default(); WORLD_SIZE.x * WORLD_SIZE.y].into_boxed_slice(); let neighbor_coef = TerrainChunkSize::RECT_SIZE.map(|e| e as f64); // (Roughly) area of a chunk, times minutes per second. - let mins_per_sec = /*64.0*/1.0; //1.0 / 64.0; + let mins_per_sec = /*64.0*/1.0/*1.0 / 16.0*/; let chunk_area_factor = neighbor_coef.x * neighbor_coef.y / mins_per_sec; // NOTE: This technically makes us discontinuous, so we should be cautious about using this. let derivative_divisor = 1.0; @@ -567,16 +567,21 @@ pub fn get_rivers, G: Float + Into>( /// Precompute the maximum slope at all points. /// /// TODO: See if allocating in advance is worthwhile. -fn get_max_slope(h: &[Alt], rock_strength_nz: &(impl NoiseFn> + Sync)) -> Box<[f64]> { +fn get_max_slope( + h: &[Alt], + rock_strength_nz: &(impl NoiseFn> + Sync), + height_scale: impl Fn(usize) -> Alt + Sync, +) -> Box<[f64]> { let min_max_angle = (15.0/*6.0*//*30.0*//*6.0*//*15.0*/ / 360.0 * 2.0 * f64::consts::PI).tan(); let max_max_angle = (60.0/*54.0*//*50.0*//*54.0*//*45.0*/ / 360.0 * 2.0 * f64::consts::PI).tan(); let max_angle_range = max_max_angle - min_max_angle; - let height_scale = 1.0 / 4.0; // 1.0; // 1.0 / CONFIG.mountain_scale as f64; + // let height_scale = 1.0 / 4.0; // 1.0; // 1.0 / CONFIG.mountain_scale as f64; h.par_iter() .enumerate() .map(|(posi, &z)| { let wposf = uniform_idx_as_vec2(posi).map(|e| e as f64) * TerrainChunkSize::RECT_SIZE.map(|e| e as f64); + let height_scale = height_scale(posi); let wposz = z as f64 / height_scale;// * CONFIG.mountain_scale as f64; // Normalized to be between 6 and and 54 degrees. let div_factor = /*32.0*//*16.0*//*64.0*//*256.0*//*8.0 / 4.0*//*8.0*/(2.0 * TerrainChunkSize::RECT_SIZE.x as f64) / 8.0/* * 8.0*//*1.0*//*4.0*//* * /*1.0*/16.0/* TerrainChunkSize::RECT_SIZE.x as f64 / 8.0 */*/; @@ -723,12 +728,16 @@ fn erode( epsilon_0: impl Fn(usize) -> f32 + Sync, alpha: impl Fn(usize) -> f32 + Sync, is_ocean: impl Fn(usize) -> bool + Sync, + // scaling factors + height_scale: impl Fn(f32) -> Alt + Sync, + k_da_scale: impl Fn(f64) -> f64, ) { let compute_stats = true; log::debug!("Done draining..."); - let height_scale = 1.0; // 1.0 / CONFIG.mountain_scale as f64; + // let height_scale = 1.0 / 4.0; // 1.0 / CONFIG.mountain_scale as f64; let min_erosion_height = 0.0; // -::infinity(); - let mmaxh = CONFIG.mountain_scale as f64 * height_scale; + + // let mmaxh = CONFIG.mountain_scale as f64 * height_scale; // Since maximum uplift rate is expected to be 5.010e-4 m * y^-1, and // 1.0 height units is 1.0 / height_scale m, whatever the // max uplift rate is (in units / y), we can find dt by multiplying by @@ -749,20 +758,21 @@ fn erode( let dt = max_uplift as f64/* / height_scale*/ /* * CONFIG.mountain_scale as f64*/ / /*5.010e-4*/1e-3/*0.2e-3*/; log::debug!("dt={:?}", dt); // Minimum sediment thickness before we treat erosion as sediment based. - let sediment_thickness = 1.0e-4 * dt; + let sediment_thickness = |_n| /*6.25e-5*/1.0e-4 * dt; let neighbor_coef = TerrainChunkSize::RECT_SIZE.map(|e| e as f64); let chunk_area = neighbor_coef.x * neighbor_coef.y; let min_length = neighbor_coef.reduce_partial_min(); let max_stable = /*max_slope * */min_length * min_length / (dt/* / 2.0*/); //1.0/* + /*max_uplift as f64 / dt*/sed / dt*/; - // Landslide constant: ideally scaled to 10e-2 m / y^-1 - let l = /*200.0 * max_uplift as f64;*/(1.0e-2 /*/ CONFIG.mountain_scale as f64*/ * height_scale); - let l_tot = l * dt; + + // Landslide constant: ideally scaled to 10e-2 m / y^-1 + // let l = /*200.0 * max_uplift as f64;*/(1.0e-2 /*/ CONFIG.mountain_scale as f64*/ * height_scale); + // let l_tot = l * dt; // Debris flow coefficient (m / year). let k_df = 1.0e-4/*0.0*/; // Debris flow area coefficient (m^(-2q)). let q = 0.2; let q_ = /*1.0*/1.5/*1.0*/; - let k_da = /*5.0*//*2.5*//*5.0*//*2.5*//*5.0*/2.5 * 4.0.powf(2.0 * q); + let k_da = /*5.0*//*2.5*//*5.0*//*2.5*//*5.0*/2.5 * k_da_scale(q); let nx = WORLD_SIZE.x; let ny = WORLD_SIZE.y; let dx = TerrainChunkSize::RECT_SIZE.x as f64/* * height_scale*//* / CONFIG.mountain_scale as f64*/; @@ -887,7 +897,8 @@ fn erode( || { rayon::join( || { - let max_slope = get_max_slope(h, rock_strength_nz); + let max_slope = + get_max_slope(h, rock_strength_nz, |posi| height_scale(n_f(posi))); log::debug!("Got max slopes..."); max_slope }, @@ -932,7 +943,8 @@ fn erode( } else { let old_b_i = b[posi]; let sed = (h_t[posi] - old_b_i) as f64; - let k = if sed > sediment_thickness { + let n = n_f(posi); + let k = if sed > sediment_thickness(n) { // Sediment // k_fs k_fs_mult_sed * kf(posi) @@ -941,7 +953,7 @@ fn erode( // k_fb kf(posi) } * dt; - let n = n_f(posi) as f64; + let n = n as f64; let m = m_f(posi) as f64; let mwrec_i = &mwrec[posi]; @@ -985,7 +997,8 @@ fn erode( kf(posi) } * dt; */ // let g_i = g(posi) as f64; - let g_i = if sed > sediment_thickness { + let n = n_f(posi); + let g_i = if sed > sediment_thickness(n) { g_fs_mult_sed * g(posi) as f64 } else { g(posi) as f64 @@ -1381,7 +1394,8 @@ fn erode( // let g_i = g(posi) as Compute; let old_b_i = b_i; let sed = (h_t_i - old_b_i) as f64; - let g_i = if sed > sediment_thickness { + let n = n_f(posi); + let g_i = if sed > sediment_thickness(n) { g_fs_mult_sed * g(posi) as Compute } else { g(posi) as Compute @@ -1463,6 +1477,7 @@ fn erode( } else { // *is_done.at(posi) = done_val; let posj = posj as usize; + let posj_stack = mstack_inv[posj]; // let dxy = (uniform_idx_as_vec2(posi) - uniform_idx_as_vec2(posj)).map(|e| e as f64); // Has an outgoing flow edge (posi, posj). @@ -1478,7 +1493,7 @@ fn erode( // h[i](t + dt) = (h[i](t) + δt * (uplift[i] + flux(i) * h[j](t + δt))) / (1 + flux(i) * δt). // NOTE: posj has already been computed since it's downhill from us. // Therefore, we can rely on wh being set to the water height for that node. - // let h_j = h[posj] as f64; + let h_j = h[posj_stack] as f64; // let a_j = a[posj] as f64; let wh_j = wh[posj] as f64; // let old_a_i = a[posi] as f64; @@ -1503,7 +1518,7 @@ fn erode( }; */ // Only perform erosion if we are above the water level of the previous node. - if old_elev_i > wh_j/*h_j*//*h[posj]*/ { + if old_elev_i > wh_j/*h_j*//*h_j*//*h[posj]*/ { let mut dtherm = 0.0f64; /* { // Thermal erosion (landslide) @@ -1565,8 +1580,9 @@ fn erode( assert!(posj_stack > stacki); */ // This can happen in cases where receiver kk is neither uphill of // nor downhill from posi's direct receiver. - if /*new_ht_i/*old_ht_i*/ >= (h_t[posj]/* + uplift(posj) as f64*/)*/old_elev_i /*>=*/> wh[posj] as f64/*h[posj]*//*h_j*/ { - let h_j = h_stack[posj_stack] as f64; + let h_j = h_stack[posj_stack] as f64; + if /*new_ht_i/*old_ht_i*/ >= (h_t[posj]/* + uplift(posj) as f64*/)*/old_elev_i /*>=*/> /*wh[posj] as f64*//*h[posj]*/h_j { + // let h_j = h_stack[posj_stack] as f64; let elev_j = h_j/* + a_j.max(0.0)*/; /* let flux = /*k * (p * chunk_area * area[posi] as f64).powf(m) / neighbor_distance;*/k_fs_fact[kk] + k_df_fact[kk]; assert!(flux.is_normal() && flux.is_positive() || flux == 0.0); @@ -1601,7 +1617,9 @@ fn erode( let mut mask = [MaskType::new(false); 8]; mrec_downhill(&mrec, posi).for_each(|(kk, posj)| { let posj_stack = mstack_inv[posj]; - if old_elev_i > wh[posj] as f64 { + let h_j = h_stack[posj_stack]; + if /*new_h_t_i > (h_t[posj] + uplift(posj)) as f64 */old_elev_i > /*wh[posj]*/h_j as f64 { + // let h_j = h_stack[posj_stack]; // k_fs_weights[kk] = k_fs_fact[kk] as SimdType; // k_fs_weights[max] = k_fs_fact[kk] as SimdType; // /*k_fs_weights = */k_fs_weights.replace(max, k_fs_fact[kk]/*.extract(kk)*/ as f64); @@ -1609,7 +1627,7 @@ fn erode( // k_df_weights[max] = k_df_fact[kk] as SimdType; // /*k_df_weights = */k_df_weights.replace(max, k_df_fact[kk]/*.extract(kk)*/ as f64); mask[kk] = MaskType::new(true); - rec_heights[kk] = h_stack[posj_stack] as SimdType; + rec_heights[kk] = h_j as SimdType; // rec_heights[max] = h[posj] as SimdType; // /*rec_heights = */rec_heights.replace(max, h[posj] as f64); // max += 1; @@ -1928,7 +1946,7 @@ fn erode( // If we dipped below the receiver's water level, set our height to the receiver's // water level. - if new_h_i <= wh_j/*elev_j*//*h[posj]*/ { + if new_h_i <= wh_j/*h_j*//*elev_j*//*h[posj]*/ { if compute_stats { ncorr += 1; } @@ -1941,7 +1959,7 @@ fn erode( // will be set precisely equal to the estimated height, meaning it // effectively "vanishes" and just deposits sediment to its reciever. // (This is probably related to criteria for block Gauss-Seidel, etc.). - new_h_i = /*h[posj];*/wh_j;// - df_part.max(0.0); + new_h_i = /*h[posj];*/wh_j/*h_j*/;// - df_part.max(0.0); // df_part = 0.0; /* let lposj = lake_sill[posj]; lake_sill[posi] = lposj; @@ -2076,7 +2094,7 @@ fn erode( sums += mag_slope; // Just use the computed rate. } */ - h_stack[/*(posi*/stacki] = new_h_i as Alt; + h_stack[/*posi*/stacki] = new_h_i as Alt; // println!("Delta: {:?} - {:?} = {:?}", new_h_i, h_p_i, new_h_i - h_p_i); // a[posi] = df_part as Alt; // Make sure to update the basement as well! @@ -2327,7 +2345,8 @@ fn erode( let kd_factor = // 1.0; (1.0 / (max_slope / mid_slope/*.sqrt()*//*.powf(0.03125)*/).powf(/*2.0*/2.0))/*.min(kdsed)*/; - max_slopes[posi] = if sed > sediment_thickness && kdsed > 0.0 { + let n = n_f(posi); + max_slopes[posi] = if sed > sediment_thickness(n) && kdsed > 0.0 { // Sediment kdsed /* * kd_factor*/ } else { @@ -3386,6 +3405,10 @@ pub fn do_erosion( g: impl Fn(usize) -> f32 + Sync, epsilon_0: impl Fn(usize) -> f32 + Sync, alpha: impl Fn(usize) -> f32 + Sync, + // scaling factors + height_scale: impl Fn(f32) -> Alt + Sync, + k_d_scale: f64, + k_da_scale: impl Fn(f64) -> f64, ) -> (Box<[Alt]>, Box<[Alt]> /*, Box<[Alt]>*/) { log::debug!("Initializing erosion arrays..."); let oldh_ = (0..WORLD_SIZE.x * WORLD_SIZE.y) @@ -3479,7 +3502,7 @@ pub fn do_erosion( // Bedrock transport coefficients (diffusivity) in m^2 / year. For now, we set them all to be equal // on land, but in theory we probably want to at least differentiate between soil, bedrock, and // sediment. - let height_scale = 1.0 / 4.0; // 1.0 / CONFIG.mountain_scale as f64; + /* let height_scale = 1.0 / 4.0; // 1.0 / CONFIG.mountain_scale as f64; let time_scale = 1.0; //1.0 / 4.0; // 4.0.powf(-n) let mmaxh = CONFIG.mountain_scale as f64 * height_scale; let dt = max_uplift as f64 / height_scale /* * CONFIG.mountain_scale as f64*/ / 5.010e-4; @@ -3487,10 +3510,12 @@ pub fn do_erosion( 2.0e-5 * dt; let kd_bedrock = /*1e-2*//*0.25e-2*/1e-2 / 1.0 * height_scale * height_scale/* / (CONFIG.mountain_scale as f64 * CONFIG.mountain_scale as f64) */ - /* * k_fb / 2e-5 */; + /* * k_fb / 2e-5 */; */ let kdsed = - /*1.5e-2*//*1e-4*//*1.25e-2*//*1.5e-2 */1.5e-2 / 1.0 * height_scale * height_scale / time_scale/* / (CONFIG.mountain_scale as f64 * CONFIG.mountain_scale as f64) */ + /*1.5e-2*//*1e-4*//*1.25e-2*//*1.5e-2 *//*1.5e-2 / 1.0*//* * height_scale * height_scale / time_scale*//* / (CONFIG.mountain_scale as f64 * CONFIG.mountain_scale as f64) */ + 1.5e-2 / 4.0 /* * k_fb / 2e-5 */; + let kdsed = kdsed * k_d_scale; // let kd = |posi: usize| kd_bedrock; // if is_ocean(posi) { /*0.0*/kd_bedrock } else { kd_bedrock }; let n = |posi: usize| n[posi]; let m = |posi: usize| m[posi]; @@ -3499,6 +3524,8 @@ pub fn do_erosion( let g = |posi: usize| g[posi]; let epsilon_0 = |posi: usize| epsilon_0[posi]; let alpha = |posi: usize| alpha[posi]; + let height_scale = |n| height_scale(n); + let k_da_scale = |q| k_da_scale(q); // Hillslope diffusion coefficient for sediment. let mut is_done = bitbox![0; WORLD_SIZE.x * WORLD_SIZE.y]; (0..n_steps).for_each(|i| { @@ -3529,6 +3556,8 @@ pub fn do_erosion( epsilon_0, alpha, |posi| is_ocean(posi), + height_scale, + k_da_scale, ); }); (h, b /*, a*/) diff --git a/world/src/sim/mod.rs b/world/src/sim/mod.rs index af680e132d..97d5e2b3cd 100644 --- a/world/src/sim/mod.rs +++ b/world/src/sim/mod.rs @@ -30,6 +30,7 @@ use crate::{ CONFIG, }; use common::{ + assets, terrain::{BiomeKind, TerrainChunkSize}, vol::RectVolSize, }; @@ -113,6 +114,7 @@ pub(crate) struct GenCtx { pub uplift_nz: Worley, } +#[derive(Clone, Debug, Deserialize, Serialize)] pub enum FileOpts { /// If set, generate the world map and do not try to save to or load from file /// (default). @@ -120,8 +122,17 @@ pub enum FileOpts { /// If set, generate the world map and save the world file (path is created /// the same way screenshot paths are). Save, + /// If set, load the world file from this path in legacy format (errors if + /// path not found). This option may be removed at some point, since it only applies to maps + /// generated before map saving was merged into master. + LoadLegacy(PathBuf), /// If set, load the world file from this path (errors if path not found). Load(PathBuf), + /// If set, look for the world file at this asset specifier (errors if asset is not found). + /// + /// NOTE: Could stand to merge this with `Load` and construct an enum that can handle either a + /// PathBuf or an asset specifier, at some point. + LoadAsset(String), } impl Default for FileOpts { @@ -145,17 +156,131 @@ impl Default for WorldOpts { } } -/// A way to store certain components between runs of map generation. Only intended for -/// development purposes--no attempt is made to detect map invalidation or make sure that the map -/// is synchronized with updates to noise-rs, changes to other parameters, etc. +/// LEGACY: Remove when people stop caring. #[derive(Serialize, Deserialize)] -pub struct WorldFile { +#[repr(C)] +pub struct WorldFileLegacy { /// Saved altitude height map. pub alt: Box<[Alt]>, /// Saved basement height map. pub basement: Box<[Alt]>, } +/// Version of the world map intended for use in Veloren 0.5.0. +#[derive(Serialize, Deserialize)] +#[repr(C)] +pub struct WorldMap_0_5_0 { + /// Saved altitude height map. + pub alt: Box<[Alt]>, + /// Saved basement height map. + pub basement: Box<[Alt]>, +} + +/// Errors when converting a map to the most recent type (currently, +/// shared by the various map types, but at some point we might switch to +/// version-specific errors if it feels worthwhile). +#[derive(Debug)] +pub enum WorldFileError { + /// Map size was invalid, and it can't be converted to a valid one. + WorldSizeInvalid, +} + +/// WORLD MAP. +/// +/// A way to store certain components between runs of map generation. Only intended for +/// development purposes--no attempt is made to detect map invalidation or make sure that the map +/// is synchronized with updates to noise-rs, changes to other parameters, etc. +/// +/// The map is verisoned to enable format detection between versions of Veloren, so that when we +/// update the map format we don't break existing maps (or at least, we will try hard not to break +/// maps between versions; if we can't avoid it, we can at least give a reasonable error message). +/// +/// NOTE: We rely somemwhat heavily on the implementation specifics of bincode to make sure this is +/// backwards compatible. When adding new variants here, Be very careful to make sure tha the old +/// variants are preserved in the correct order and with the correct names and indices, and make +/// sure to keep the #[repr(u32)]! +/// +/// All non-legacy versions of world files should (ideally) fit in this format. Since the format +/// contains a version and is designed to be extensible backwards-compatibly, the only +/// reason not to use this forever would be if we decided to move away from BinCode, or +/// store data across multiple files (or something else weird I guess). +/// +/// Update this when you add a new map version. +#[derive(Serialize, Deserialize)] +#[repr(u32)] +pub enum WorldFile { + Veloren_0_5_0(WorldMap_0_5_0) = 0, +} + +/// Data for the most recent map type. Update this when you add a new map verson. +pub type ModernMap = WorldMap_0_5_0; + +impl WorldFileLegacy { + #[inline] + /// Idea: each map type except the latest knows how to transform + /// into the the subsequent map version, and each map type including the + /// latest exposes an "into_modern()" method that converts this map type + /// to the modern map type. Thus, to migrate a map from an old format to a new + /// format, we just need to transform the old format to the subsequent map + /// version, and then call .into_modern() on that--this should construct a call chain that + /// ultimately ends up with a modern version. + pub fn into_modern(self) -> Result { + if self.alt.len() != self.basement.len() + || self.alt.len() != WORLD_SIZE.x as usize * WORLD_SIZE.y as usize + { + return Err(WorldFileError::WorldSizeInvalid); + } + + /* let f = |h| h;// / 4.0; + let mut map = map; + map.alt.par_iter_mut() + .zip(map.basement.par_iter_mut()) + .for_each(|(mut h, mut b)| { + *h = f(*h); + *b = f(*b); + }); */ + + let map = WorldMap_0_5_0 { + alt: self.alt, + basement: self.basement, + }; + + map.into_modern() + } +} + +impl WorldMap_0_5_0 { + #[inline] + pub fn into_modern(self) -> Result { + if self.alt.len() != self.basement.len() + || self.alt.len() != WORLD_SIZE.x as usize * WORLD_SIZE.y as usize + { + return Err(WorldFileError::WorldSizeInvalid); + } + + Ok(self) + } +} + +impl WorldFile { + /// Turns map data from the latest version into a versioned WorldFile ready for serialization. + /// Whenever a new map is updated, just change the variant we construct here to make sure we're + /// using the latest map version. + + pub fn new(map: ModernMap) -> Self { + WorldFile::Veloren_0_5_0(map) + } + + #[inline] + /// Turns a WorldFile into the latest version. Whenever a new map version is added, just add + /// it to this match statement. + pub fn into_modern(self) -> Result { + match self { + WorldFile::Veloren_0_5_0(map) => map.into_modern(), + } + } +} + pub struct WorldSim { pub seed: u32, pub(crate) chunks: Vec, @@ -291,8 +416,48 @@ impl WorldSim { let rock_strength_nz = ScaleBias::new(&rock_strength_nz) .set_scale(1.0 / rock_strength_scale); */ - let height_scale = 1.0f64; // 1.0 / CONFIG.mountain_scale as f64; - let max_erosion_per_delta_t = /*8.0*//*32.0*//*1.0*//*32.0*//*32.0*//*16.0*//*64.0*//*32.0*/16.0/*128.0*//*1.0*//*0.2 * /*100.0*/250.0*//*128.0*//*16.0*//*128.0*//*32.0*/ * height_scale; + // Suppose the old world has grid spacing Δx' = Δy', new Δx = Δy. + // We define grid_scale such that Δx = height_scale * Δx' ⇒ + // grid_scale = Δx / Δx'. + let grid_scale = 1.0f64 / 4.0/*1.0*/; + // Now, suppose we want to generate a world with "similar" topography, defined in this case + // as having roughly equal slopes at steady state, with the simulation taking roughly as + // many steps to get to the point the previous world was at when it finished being + // simulated. + // + // Some computations with our coupled SPL/debris flow give us (for slope S constant) the following + // suggested scaling parameters to make this work: + // k_fs_scale ≡ (K𝑓 / K𝑓') = grid_scale^(-2m) = grid_scale^(-2θn) + let k_fs_scale = |theta, n| grid_scale.powf(-2.0 * (theta * n) as f64); + + // k_da_scale ≡ (K_da / K_da') = grid_scale^(-2q) + let k_da_scale = |q| grid_scale.powf(-2.0 * q); + // + // Some other estimated parameters are harder to come by and *much* more dubious, not being accurate + // for the coupled equation. But for the SPL only one we roughly find, for h the height at steady + // state and time τ = time to steady state, with Hack's Law estimated b = 2.0 and various other + // simplifying assumptions, the estimate: + // height_scale ≡ (h / h') = grid_scale^(n) + let height_scale = |n: f32| grid_scale.powf(n as f64) as Alt; + // time_scale ≡ (τ / τ') = grid_scale^(n) + let time_scale = |n: f32| grid_scale.powf(n as f64); + // + // Based on this estimate, we have: + // delta_t_scale ≡ (Δt / Δt') = time_scale + let delta_t_scale = |n: f32| time_scale(n); + // alpha_scale ≡ (α / α') = height_scale^(-1) + let alpha_scale = |n: f32| height_scale(n).recip() as f32; + // + // Slightly more dubiously (need to work out the math better) we find: + // k_d_scale ≡ (K_d / K_d') = grid_scale^2 / (height_scale * time_scale) + let k_d_scale = |n: f32| /*grid_scale.powi(2) / time_scale(n)*//*height_scale(n)*/grid_scale.powi(2) / (/*height_scale(n) * */time_scale(n))/* * (1.0 / 16.0)*/; + // epsilon_0_scale ≡ (ε₀ / ε₀') = height_scale(n) / time_scale(n) + let epsilon_0_scale = |n| /*height_scale(n) as f32*//*1.0*/(height_scale(n) / time_scale(n)) as f32/* * 1.0 / 4.0*/; + + // Approximate n for purposes of computation of parameters above over the whole grid (when + // a chunk isn't available). + let n_approx = 1.0; + let max_erosion_per_delta_t = /*8.0*//*32.0*//*1.0*//*32.0*//*32.0*//*16.0*//*64.0*//*32.0*/64.0/*128.0*//*1.0*//*0.2 * /*100.0*/250.0*//*128.0*//*16.0*//*128.0*//*32.0*/ * delta_t_scale(n_approx); 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; @@ -531,15 +696,56 @@ impl WorldSim { }); // Calculate oceans. - let old_height = - |posi: usize| alt_old[posi].1 * CONFIG.mountain_scale * height_scale as f32; + let is_ocean = get_oceans(|posi: usize| alt_old[posi].1); + let is_ocean_fn = |posi: usize| is_ocean[posi]; /* let is_ocean = (0..WORLD_SIZE.x * WORLD_SIZE.y) .into_par_iter() .map(|i| map_edge_factor(i) == 0.0) .collect::>(); */ - let is_ocean = get_oceans(old_height); - let is_ocean_fn = |posi: usize| is_ocean[posi]; + let turb_wposf_div = 8.0/*64.0*/; + let n_func = |posi| { + if is_ocean_fn(posi) { + return 1.0; + } + let wposf = (uniform_idx_as_vec2(posi) * TerrainChunkSize::RECT_SIZE.map(|e| e as i32)) + .map(|e| e as f64); + let turb_wposf = wposf + .div(TerrainChunkSize::RECT_SIZE.map(|e| e as f64)) + .div(turb_wposf_div); + let turb = Vec2::new( + gen_ctx.turb_x_nz.get(turb_wposf.into_array()), + gen_ctx.turb_y_nz.get(turb_wposf.into_array()), + ) * uplift_turb_scale + * TerrainChunkSize::RECT_SIZE.map(|e| e as f64); + // let turb = Vec2::zero(); + let turb_wposf = wposf + turb; + let turb_wposi = turb_wposf + .map2(TerrainChunkSize::RECT_SIZE, |e, f| e / f as f64) + .map2(WORLD_SIZE, |e, f| (e as i32).max(f as i32 - 1).min(0)); + let turb_posi = vec2_as_uniform_idx(turb_wposi); + let uheight = gen_ctx + .uplift_nz + .get(turb_wposf.into_array()) + /* .min(0.5) + .max(-0.5)*/ + .min(1.0) + .max(-1.0) + .mul(0.5) + .add(0.5); + /* if uheight > 0.8 { + 1.5 + } else { + 1.0 + } */ + // ((1.5 - 0.6) * uheight + 0.6) as f32 + // ((1.5 - 1.0) * uheight + 1.0) as f32 + // ((3.5 - 1.5) * (1.0 - uheight) + 1.5) as f32 + 1.0 + }; + let old_height = |posi: usize| { + alt_old[posi].1 * CONFIG.mountain_scale * height_scale(n_func(posi)) as f32 + }; let uplift_nz_dist = gen_ctx.uplift_nz.clone().enable_range(true); // Recalculate altitudes without oceans. @@ -694,58 +900,19 @@ impl WorldSim { 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*//*logit*/inv_func(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))*//*.powf(0.5)*//*.powf(1.5)*//*.powf(2.0)*/; let rock_strength_div_factor = /*8.0*/(2.0 * TerrainChunkSize::RECT_SIZE.x as f64) / 8.0; - let time_scale = 1.0; //4.0/*4.0*/; - let n_func = |posi| { - if is_ocean_fn(posi) { - return 1.0; - } - let wposf = (uniform_idx_as_vec2(posi) * TerrainChunkSize::RECT_SIZE.map(|e| e as i32)) - .map(|e| e as f64); - let turb_wposf = wposf - .div(TerrainChunkSize::RECT_SIZE.map(|e| e as f64)) - .div(turb_wposf_div); - let turb = Vec2::new( - gen_ctx.turb_x_nz.get(turb_wposf.into_array()), - gen_ctx.turb_y_nz.get(turb_wposf.into_array()), - ) * uplift_turb_scale - * TerrainChunkSize::RECT_SIZE.map(|e| e as f64); - // let turb = Vec2::zero(); - let turb_wposf = wposf + turb; - let turb_wposi = turb_wposf - .map2(TerrainChunkSize::RECT_SIZE, |e, f| e / f as f64) - .map2(WORLD_SIZE, |e, f| (e as i32).max(f as i32 - 1).min(0)); - let turb_posi = vec2_as_uniform_idx(turb_wposi); - let uheight = gen_ctx - .uplift_nz - .get(turb_wposf.into_array()) - /* .min(0.5) - .max(-0.5)*/ - .min(1.0) - .max(-1.0) - .mul(0.5) - .add(0.5); - /* if uheight > 0.8 { - 1.5 - } else { - 1.0 - } */ - // ((1.5 - 0.6) * uheight + 0.6) as f32 - // ((1.5 - 1.0) * uheight + 1.0) as f32 - // ((3.5 - 1.5) * (1.0 - uheight) + 1.5) as f32 - 1.0 - }; + // let time_scale = 1.0; //4.0/*4.0*/; let theta_func = |posi| 0.4; let kf_func = { |posi| { - let m_i = (theta_func(posi) * n_func(posi)) as f64; + let kf_scale_i = k_fs_scale(theta_func(posi), n_func(posi)) as f64; // let precip_mul = (0.25).powf(m); if is_ocean_fn(posi) { // multiplied by height_scale^(2m) to account for change in area. - return 1.0e-4 * 4.0.powf(2.0 * m_i)/* / time_scale*/; // .powf(-(1.0 - 2.0 * m_i))/* * 4.0*/; - // return 2.0e-5; - // return 2.0e-6; - // return 2.0e-10; - // return 0.0; + return 1.0e-4 * kf_scale_i/* / time_scale*/; // .powf(-(1.0 - 2.0 * m_i))/* * 4.0*/; + // return 2.0e-5; + // return 2.0e-6; + // return 2.0e-10; + // return 0.0; } let wposf = (uniform_idx_as_vec2(posi) * TerrainChunkSize::RECT_SIZE.map(|e| e as i32)) @@ -784,7 +951,7 @@ impl WorldSim { let oheight = /*alt_old*//*alt_base*/alt_old_no_ocean[/*(turb_posi / 64) * 64*/posi].0 as f64; let oheight_2 = /*alt_old*//*alt_base*/(alt_old_no_ocean[/*(turb_posi / 64) * 64*/posi].1 as f64 / CONFIG.mountain_scale as f64); - // kf = 1.5e-4: high-high (plateau [fan sediment]) + let kf_i = // kf = 1.5e-4: high-high (plateau [fan sediment]) // kf = 1e-4: high (plateau) // kf = 2e-5: normal (dike [unexposed]) // kf = 1e-6: normal-low (dike [exposed]) @@ -808,17 +975,26 @@ impl WorldSim { // ((1.0 - uheight) * (0.5 - 0.5 * /*((1.32 - uchaos as f64) / 1.32)*/oheight) * (1.5e-4 - 2.0e-6) + 2.0e-6) // 2e-5 // multiplied by height_scale^(2m) to account for change in area. - 2.5e-6 * 4.0.powf(/*-(1.0 - 2.0 * m_i)*/ 2.0 * m_i) /* / time_scale*//* / 4.0 * 0.25 *//* * 4.0*/ + // 2.5e-6/* / time_scale*//* / 4.0 * 0.25 *//* * 4.0*/ + 1.0e-6 + // 2.0e-6 // 2.9e-10 // ((1.0 - uheight) * (5e-5 - 2.9e-10) + 2.9e-10) // ((1.0 - uheight) * (5e-5 - 2.9e-14) + 2.9e-14) + ; + kf_i * kf_scale_i } }; let kd_func = { |posi| { - // let height_scale = 1.0 / 4.0.powf(-n_i); + let n = n_func(posi); + let kd_scale_i = k_d_scale(n); if is_ocean_fn(posi) { - return 1.0e-2 * 4.0.powf(-2.0) * time_scale; + let kd_i = + /*1.0e-2*/ + 1.0e-2 / 4.0 + ; + kd_i * kd_scale_i; } let wposf = (uniform_idx_as_vec2(posi) * TerrainChunkSize::RECT_SIZE.map(|e| e as i32)) @@ -846,13 +1022,25 @@ impl WorldSim { .max(-1.0) .mul(0.5) .add(0.5); + let uchaos = /* gen_ctx.chaos_nz.get((wposf.div(3_000.0)).into_array()) + .min(1.0) + .max(-1.0) + .mul(0.5) + .add(0.5); */ + chaos[posi].1; + // kd = 1e-1: high (mountain, dike) // kd = 1.5e-2: normal-high (plateau [fan sediment]) // kd = 1e-2: normal (plateau) // multiplied by height_scale² to account for change in area, then divided by // time_scale to account for lower dt. - 1.0e-2 * 4.0.powf(-2.0) * time_scale // m_old^2 / y * (1 m_new / 4 m_old)^2 - // (uheight * (1.0e-1 - 1.0e-2) + 1.0e-2) + let kd_i = // 1.0e-2 * kd_scale_i;// m_old^2 / y * (1 m_new / 4 m_old)^2 + 1.10e-2 / 4.0 + // (uheight * (1.0e-1 - 1.0e-2) + 1.0e-2) + // ((1.0 - uheight) * (0.5 + 0.5 * ((1.32 - uchaos as f64) / 1.32)) * (1.0e-2 - 1.0e-3) + 1.0e-3) + // (uheight * (1.0e-2 - 1.0e-3) + 1.0e-3) / 2.0 + ; + kd_i * kd_scale_i } }; let g_func = |posi| { @@ -922,10 +1110,9 @@ impl WorldSim { // 1.5 }; let epsilon_0_func = |posi| { - let n_i = n_func(posi); - // height_scale is roughly [using Hack's Law with b = 2 and SPL without debris flow or + // epsilon_0_scale is roughly [using Hack's Law with b = 2 and SPL without debris flow or // hillslopes] equal to the ratio of the old to new area, to the power of -n_i. - let height_scale = 4.0.powf(-n_i); + let epsilon_0_scale_i = epsilon_0_scale(n_func(posi)); if is_ocean_fn(posi) { // marine: ε₀ = 2.078e-3 // divide by height scale, multiplied by time_scale, cancels out to 1; idea is that @@ -952,7 +1139,11 @@ impl WorldSim { // // ε₀ e^(-α' H') (Δt' * height_scale) / height_scale = ε₀' e^(-α' H') Δt' // ε₀ = ε₀' - return 2.078e-3 * height_scale/* * time_scale*/; + let epsilon_0_i = + //2.078e-3 + 2.078e-3 / 4.0 + ; + return epsilon_0_i * epsilon_0_scale_i/* * time_scale*/; // return 5.0; } let wposf = (uniform_idx_as_vec2(posi) * TerrainChunkSize::RECT_SIZE.map(|e| e as i32)) @@ -980,6 +1171,9 @@ impl WorldSim { .max(-1.0) .mul(0.5) .add(0.5); + /* let n_i = n_func(posi); + let height_scale = height_scale(n_i); + let uheight = uheight / height_scale; */ let wposf3 = Vec3::new( wposf.x, wposf.y, @@ -994,9 +1188,9 @@ impl WorldSim { .max(-1.0) .mul(0.5) .add(0.5); - let center = /*0.25*/0.4 / 4.0; - let dmin = center - /*0.15;//0.05*/0.05 / 4.0; - let dmax = center + /*0.05*//*0.10*/0.05 / 4.0; //0.05; + let center = /*0.25*/0.4; + let dmin = center - /*0.15;//0.05*/0.05; + let dmax = center + /*0.05*//*0.10*/0.05; //0.05; let log_odds = |x: f64| logit(x) - logit(center); let ustrength = logistic_cdf( 1.0 * logit(rock_strength.min(1.0f64 - 1e-7).max(1e-7)) @@ -1010,25 +1204,28 @@ impl WorldSim { // Nunnock River (fractured granite, least weathered?): ε₀ = 5.3e-5 // The stronger the rock, the lower the production rate of exposed bedrock. // divide by height scale, then multiplied by time_scale, cancels out. - ((1.0 - ustrength) * (/*3.18e-4*/2.078e-3 - 5.3e-5) + 5.3e-5) as f32 * height_scale + let epsilon_0_i = + // ((1.0 - ustrength) * (/*3.18e-4*/2.078e-3 - 5.3e-5) + 5.3e-5) as f32 + ((1.0 - ustrength) * (/*3.18e-4*/2.078e-3 - 5.3e-5) + 5.3e-5) as f32 / 4.0 + ; /* * time_scale*/ // 0.0 + ; + epsilon_0_i * epsilon_0_scale_i }; let alpha_func = |posi| { - let n_i = n_func(posi); // height_scale is roughly [using Hack's Law with b = 2 and SPL without debris flow or // hillslopes] equal to the ratio of the old to new area, to the power of -n_i. - let height_scale = 4.0.powf(-n_i); + // the old height * height scale, and we take the rate as ε₀ * e^(-αH), to keep + // the rate of rate of change in soil production consistent we must divide H by + // height_scale. + // + // αH = α(H' * height_scale) = α'H' + // α = α' / height_scale + let alpha_scale_i = alpha_scale(n_func(posi)); if is_ocean_fn(posi) { // marine: α = 3.7e-2 - // divided by height_scale; idea is that since the final height itself will be - // the old height * height scale, and we take the rate as ε₀ * e^(-αH), to keep - // the rate of rate of change in soil production consistent we must divide H by - // height_scale. - // - // αH = α(H' * height_scale) = α'H' - // α = α' / height_scale - return 3.7e-2 / height_scale; + return 3.7e-2 * alpha_scale_i; } let wposf = (uniform_idx_as_vec2(posi) * TerrainChunkSize::RECT_SIZE.map(|e| e as i32)) .map(|e| e as f64); @@ -1055,6 +1252,9 @@ impl WorldSim { .max(-1.0) .mul(0.5) .add(0.5); + /* let n_i = n_func(posi); + let height_scale = height_scale(n_i); + let uheight = uheight / height_scale; */ let wposf3 = Vec3::new( wposf.x, wposf.y, @@ -1069,9 +1269,9 @@ impl WorldSim { .max(-1.0) .mul(0.5) .add(0.5); - let center = /*0.25*/0.4 / 4.0; - let dmin = center - /*0.15;//0.05*/0.05 / 4.0; - let dmax = center + /*0.05*//*0.10*/0.05 / 4.0; //0.05; + let center = /*0.25*/0.4; + let dmin = center - /*0.15;//0.05*/0.05; + let dmax = center + /*0.05*//*0.10*/0.05; //0.05; let log_odds = |x: f64| logit(x) - logit(center); let ustrength = logistic_cdf( 1.0 * logit(rock_strength.min(1.0f64 - 1e-7).max(1e-7)) @@ -1084,8 +1284,8 @@ impl WorldSim { // Nunnock river (fractured granite, least weathered?): α = 2e-3 // Point Reyes: α = 1.6e-2 // The stronger the rock, the faster the decline in soil production. - // divided by height_scale. - (ustrength * (4.2e-2 - 1.6e-2) + 1.6e-2) as f32 / height_scale + let alpha_i = (ustrength * (4.2e-2 - 1.6e-2) + 1.6e-2) as f32; + alpha_i * alpha_scale_i }; let uplift_fn = |posi| { if is_ocean_fn(posi) { @@ -1304,45 +1504,85 @@ impl WorldSim { .collect::>(); let is_ocean_fn = |posi: usize| is_ocean[posi]; */ - // Load map, if necessary. + // Parse out the contents of various map formats into the values we need. let parsed_world_file = (|| { - if let FileOpts::Load(ref path) = opts.world_file { - let file = match File::open(path) { - Ok(file) => file, - Err(err) => { - log::warn!("Couldn't read path for maps: {:?}", err); - return None; - } - }; + let map = match opts.world_file { + FileOpts::LoadLegacy(ref path) => { + let file = match File::open(path) { + Ok(file) => file, + Err(err) => { + log::warn!("Couldn't read path for maps: {:?}", err); + return None; + } + }; - let reader = BufReader::new(file); - let map: WorldFile = match bincode::deserialize_from(reader) { - Ok(map) => map, - Err(err) => { - log::warn!("Couldn't parse map: {:?})", err); - return None; - } - }; + let reader = BufReader::new(file); + let map: WorldFileLegacy = match bincode::deserialize_from(reader) { + Ok(map) => map, + Err(err) => { + log::warn!("Couldn't parse legacy map: {:?}). Maybe you meant to try a regular load?", err); + return None; + } + }; - if map.alt.len() != map.basement.len() - || map.alt.len() != WORLD_SIZE.x as usize * WORLD_SIZE.y as usize - { - log::warn!("World size of map is invalid."); - return None; + map.into_modern() } + FileOpts::Load(ref path) => { + let file = match File::open(path) { + Ok(file) => file, + Err(err) => { + log::warn!("Couldn't read path for maps: {:?}", err); + return None; + } + }; - /* let f = |h| h;// / 4.0; - let mut map = map; - map.alt.par_iter_mut() - .zip(map.basement.par_iter_mut()) - .for_each(|(mut h, mut b)| { - *h = f(*h); - *b = f(*b); - }); */ + let reader = BufReader::new(file); + let map: WorldFile = match bincode::deserialize_from(reader) { + Ok(map) => map, + Err(err) => { + log::warn!("Couldn't parse modern map: {:?}). Maybe you meant to try a legacy load?", err); + return None; + } + }; - Some(map) - } else { - None + map.into_modern() + } + FileOpts::LoadAsset(ref specifier) => { + let reader = match assets::load_file(specifier, &["bin"]) { + Ok(reader) => reader, + Err(err) => { + log::warn!( + "Couldn't read asset specifier {:?} for maps: {:?}", + specifier, + err + ); + return None; + } + }; + + let map: WorldFile = match bincode::deserialize_from(reader) { + Ok(map) => map, + Err(err) => { + log::warn!("Couldn't parse modern map: {:?}). Maybe you meant to try a legacy load?", err); + return None; + } + }; + + map.into_modern() + } + FileOpts::Generate | FileOpts::Save => return None, + }; + + match map { + Ok(map) => Some(map), + Err(e) => { + match e { + WorldFileError::WorldSizeInvalid => { + log::warn!("World size of map is invalid."); + } + } + None + } } })(); @@ -1358,7 +1598,9 @@ impl WorldSim { max_erosion_per_delta_t as f32, n_steps, &river_seed, + // varying conditions &rock_strength_nz, + // initial conditions |posi| alt_func(posi), // + if is_ocean_fn(posi) { 0.0 } else { 128.0 }, |posi| { alt_func(posi) @@ -1371,6 +1613,7 @@ impl WorldSim { }, // if is_ocean_fn(posi) { old_height(posi) } else { 0.0 }, // |posi| 0.0, is_ocean_fn, + // empirical constants uplift_fn, |posi| n_func(posi), |posi| theta_func(posi), @@ -1379,12 +1622,16 @@ impl WorldSim { |posi| g_func(posi), |posi| epsilon_0_func(posi), |posi| alpha_func(posi), + // scaling factors + |n| height_scale(n), + k_d_scale(n_approx), + |q| k_da_scale(q), ); // Quick "small scale" erosion cycle in order to lower extreme angles. do_erosion( 0.0, - (1.0 * height_scale) as f32, + (1.0/* * height_scale*/) as f32, n_small_steps, &river_seed, &rock_strength_nz, @@ -1392,7 +1639,7 @@ impl WorldSim { |posi| basement[posi] as f32, // |posi| /*alluvium[posi] as f32*/0.0f32, is_ocean_fn, - |posi| uplift_fn(posi) * (1.0 * height_scale / max_erosion_per_delta_t), + |posi| uplift_fn(posi) * (1.0/* * height_scale*/ / max_erosion_per_delta_t), |posi| n_func(posi), |posi| theta_func(posi), |posi| kf_func(posi), @@ -1400,11 +1647,15 @@ impl WorldSim { |posi| g_func(posi), |posi| epsilon_0_func(posi), |posi| alpha_func(posi), + |n| height_scale(n), + k_d_scale(n_approx), + |q| k_da_scale(q), ) }; // Save map, if necessary. - let map = WorldFile { alt, basement }; + // NOTE: We wll always save a map with latest version. + let map = WorldFile::new(ModernMap { alt, basement }); (|| { if let FileOpts::Save = opts.world_file { use std::time::SystemTime; @@ -1438,7 +1689,10 @@ impl WorldSim { } } })(); - let (alt, basement) = (map.alt, map.basement); + + // Skip validation--we just performed a no-op conversion for this map, so it had better be + // valid! + let ModernMap { alt, basement } = map.into_modern().unwrap(); // Additional small-scale eroson after map load, only used during testing. let (alt, basement /*, alluvium*/) = if n_post_load_steps == 0 { @@ -1446,7 +1700,7 @@ impl WorldSim { } else { do_erosion( 0.0, - (1.0 * height_scale) as f32, + (1.0/* * height_scale*/) as f32, n_post_load_steps, &river_seed, &rock_strength_nz, @@ -1454,7 +1708,7 @@ impl WorldSim { |posi| basement[posi] as f32, // |posi| alluvium[posi] as f32, is_ocean_fn, - |posi| uplift_fn(posi) * (1.0 * height_scale / max_erosion_per_delta_t), + |posi| uplift_fn(posi) * (1.0/* * height_scale*/ / max_erosion_per_delta_t), |posi| n_func(posi), |posi| theta_func(posi), |posi| kf_func(posi), @@ -1462,6 +1716,9 @@ impl WorldSim { |posi| g_func(posi), |posi| epsilon_0_func(posi), |posi| alpha_func(posi), + |n| height_scale(n), + k_d_scale(n_approx), + |q| k_da_scale(q), ) }; @@ -2207,10 +2464,10 @@ impl SimChunk { panic!("Halp!"); } */ - let height_scale = 1.0; // 1.0 / CONFIG.mountain_scale; - let mut alt = CONFIG.sea_level.add(alt_pre.div(height_scale)); - let mut basement = CONFIG.sea_level.add(basement_pre.div(height_scale)); - let water_alt = CONFIG.sea_level.add(water_alt_pre.div(height_scale)); + // let height_scale = 1.0; // 1.0 / CONFIG.mountain_scale; + let mut alt = CONFIG.sea_level.add(alt_pre /*.div(height_scale)*/); + let mut basement = CONFIG.sea_level.add(basement_pre /*.div(height_scale)*/); + let water_alt = CONFIG.sea_level.add(water_alt_pre /*.div(height_scale)*/); let downhill = if downhill_pre == -2 { None } else if downhill_pre < 0 {