Add a default world map.

Also adds map versioning, proper scaling, and updates sediment
rendering.  It also tones down warp.
This commit is contained in:
Joshua Yanovski 2020-01-18 19:41:37 +01:00
parent 7f2573c561
commit d54f22c9fa
9 changed files with 475 additions and 170 deletions

1
.gitattributes vendored
View File

@ -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

BIN
assets/world/map/veloren_0_5_0_0.bin (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -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()
},

View File

@ -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<String>,
pub map_file: Option<PathBuf>,
/// 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<FileOpts>,
}
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((

View File

@ -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(

View File

@ -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)

View File

@ -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;

View File

@ -232,7 +232,7 @@ pub fn get_rivers<F: fmt::Debug + Float + Into<f64>, G: Float + Into<f64>>(
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<F: fmt::Debug + Float + Into<f64>, G: Float + Into<f64>>(
/// 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<Point3<f64>> + Sync)) -> Box<[f64]> {
fn get_max_slope(
h: &[Alt],
rock_strength_nz: &(impl NoiseFn<Point3<f64>> + 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; // -<Alt as Float>::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*/)

View File

@ -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<ModernMap, WorldFileError> {
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<ModernMap, WorldFileError> {
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<ModernMap, (WorldFileError)> {
match self {
WorldFile::Veloren_0_5_0(map) => map.into_modern(),
}
}
}
pub struct WorldSim {
pub seed: u32,
pub(crate) chunks: Vec<SimChunk>,
@ -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::<Vec<_>>(); */
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::<Vec<_>>();
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 {