Sediment transport, plus many other things.

This commit is contained in:
Joshua Yanovski 2019-12-03 02:07:44 +01:00
parent 067429d13e
commit e71f145b71
11 changed files with 995 additions and 328 deletions

View File

@ -46,7 +46,7 @@ use std::{
};
use uvth::{ThreadPool, ThreadPoolBuilder};
use vek::*;
use world::{sim::WORLD_SIZE, World};
use world::{sim::{WORLD_SIZE, WorldOpts}, World};
const CLIENT_TIMEOUT: f64 = 20.0; // Seconds
pub enum Event {
@ -104,7 +104,10 @@ impl Server {
state.ecs_mut().register::<RegionSubscription>();
state.ecs_mut().register::<Client>();
let world = World::generate(settings.world_seed);
let world = World::generate(settings.world_seed, WorldOpts {
seed_elements: true,
..WorldOpts::default()
});
let spawn_point = {
// NOTE: all of these `.map(|e| e as [type])` calls should compile into no-ops,

View File

@ -1,12 +1,15 @@
use std::ops::{Add, Mul, Sub};
use vek::*;
use veloren_world::{util::Sampler, World};
use veloren_world::{sim::WorldOpts, util::Sampler, World};
const W: usize = 640;
const H: usize = 480;
fn main() {
let world = World::generate(0);
let world = World::generate(0, WorldOpts {
seed_elements: true,
..WorldOpts::default()
});
let sampler = world.sample_columns();

View File

@ -1,37 +1,70 @@
use common::{terrain::TerrainChunkSize, vol::RectVolSize};
use std::f32;
// use self::Mode::*;
use std::{f32, f64};
use vek::*;
use veloren_world::{
sim::{RiverKind, WORLD_SIZE},
sim::{RiverKind, WorldOpts, WORLD_SIZE},
util::Sampler,
World, CONFIG,
};
const W: usize = 1024;
const H: usize = 1024;
/* enum Mode {
/// Directional keys affect position of the camera.
///
/// (W A S D move left and right, F B zoom in and out).
Alt,
/// Directional keys affect angle of the lens
///
/// (W
Lens,
/// Directional keys affect light direction.
///
/// (W A S D move left and right, F B move towards and awaay).
Light,
}; */
fn main() {
pretty_env_logger::init();
let world = World::generate(1337);
let world = World::generate(1337, WorldOpts {
seed_elements: false,
..WorldOpts::default()
});
let sampler = world.sim();
let mut win =
minifb::Window::new("World Viewer", W, H, minifb::WindowOptions::default()).unwrap();
let mut focus = Vec2::zero();
let mut focus = Vec3::new(0.0, 0.0, CONFIG.sea_level as f64);
// Altitude is divided by gain and clamped to [0, 1]; thus, decreasing gain makes
// smaller differences in altitude appear larger.
let mut gain = CONFIG.mountain_scale;
let mut scale = (WORLD_SIZE.x / W) as i32;
// The Z component during normal calculations is multiplied by gain; thus,
let mut lgain = 1.0;
let mut scale = (WORLD_SIZE.x / W) as f64;
let light_direction = Vec3::new(-0.8, -1.0, 0.3).normalized();
// Right-handed coordinate system: light is going left, down, and "backwards" (i.e. on the
// map, where we translate the y coordinate on the world map to z in the coordinate system,
// the light comes from -y on the map and points towards +y on the map). In a right
// handed coordinate system, the "camera" points towards -z, so positive z is backwards
// "into" the camera.
//
// "In world space the x-axis will be pointing east, the y-axis up and the z-axis will be pointing south"
let mut light_direction = Vec3::new(-0.8, -1.0, 0.3);
let light_res = 3;
let mut is_basement = false;
let mut is_water = true;
let mut is_shaded = true;
let mut is_temperature = true;
let mut is_humidity = true;
while win.is_open() {
let light = light_direction.normalized();
let mut buf = vec![0; W * H];
const QUADRANTS: usize = 4;
let mut quads = [[0u32; QUADRANTS]; QUADRANTS];
@ -40,10 +73,12 @@ fn main() {
let mut oceans = 0u32;
// let water_light = (light_direction.z + 1.0) / 2.0 * 0.8 + 0.2;
let focus_rect = Vec2::from(focus);
let true_sea_level = (CONFIG.sea_level as f64 - focus.z) / gain as f64;
for i in 0..W {
for j in 0..H {
let pos = focus + Vec2::new(i as i32, j as i32) * scale;
let pos = (focus_rect + Vec2::new(i as f64, j as f64) * scale).map(|e: f64| e as i32);
/* 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; */
@ -90,26 +125,53 @@ fn main() {
.get(cross_pos)
.map(|s| if is_basement { s.basement } else { s.alt })
.unwrap_or(CONFIG.sea_level);
// Pointing downhill, forward
// (index--note that (0,0,1) is backward right-handed)
let forward_vec = Vec3::new(
(downhill_pos.x - pos.x) as f64,
(downhill_alt - alt) as f64 * lgain,
(downhill_pos.y - pos.y) as f64,
(downhill_alt - alt) as f64 * (CONFIG.mountain_scale as f64 / gain as f64),
);
// Pointing 90 degrees left (in horizontal xy) of downhill, up
// (middle--note that (1,0,0), 90 degrees CCW backward, is right right-handed)
let up_vec = Vec3::new(
(cross_pos.x - pos.x) as f64,
(cross_alt - alt) as f64 * lgain,
(cross_pos.y - pos.y) as f64,
(cross_alt - alt) as f64 * (CONFIG.mountain_scale as f64 / gain as f64),
);
// Then cross points "to the right" (upwards) on a right-handed coordinate system.
// (right-handed coordinate system means (0, 0, 1.0) is "forward" into the screen).
let surface_normal = forward_vec.cross(up_vec).normalized();
// f = (0, alt_bl - alt_tl, 1) [backward right-handed = (0,0,1)]
// u = (1, alt_tr - alt_tl, 0) [right (90 degrees CCW backward) = (1,0,0)]
// (f × u in right-handed coordinate system: pointing up)
//
// f × u =
// (a.y*b.z - a.z*b.y,
// a.z*b.x - a.x*b.z,
// a.x*b.y - a.y*b.x,
// )
// =
// (-(alt_tr - alt_tl),
// 1,
// -(alt_bl - alt_tl),
// )
// =
// (alt_tl - alt_tr,
// 1,
// alt_tl - alt_bl,
// )
//
// 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 light = (surface_normal.dot(light) + 1.0) / 2.0;
let light = (light * 0.9) + 0.1;
let water_alt = ((alt.max(water_alt) - CONFIG.sea_level) as f64 / gain as f64)
let true_water_alt = (alt.max(water_alt) as f64 - focus.z) / gain as f64;
let true_alt = (alt as f64 - focus.z) / gain as f64;
let water_depth = (true_water_alt - true_alt)
.min(1.0)
.max(0.0);
let true_alt = (alt - CONFIG.sea_level) as f64 / gain as f64;
let water_depth = (water_alt - true_alt)
let water_alt = true_water_alt
.min(1.0)
.max(0.0);
let alt = true_alt
@ -133,20 +195,8 @@ fn main() {
None => {}
}
buf[j * W + i] = match river_kind {
Some(RiverKind::Ocean) => u32::from_le_bytes([
((64.0 - water_depth * 64.0) * 1.0) as u8,
((32.0 - water_depth * 32.0) * 1.0) as u8,
0,
255,
]),
Some(RiverKind::River { .. }) => u32::from_le_bytes([
64 + (alt * 191.0) as u8,
32 + (alt * 95.0) as u8,
0,
255,
]),
None if water_alt >= water_depth => {
buf[j * W + i] = match (river_kind, (is_water, true_alt >= true_sea_level)) {
(_, (false, _)) | ( None, (_, true)) => {
let (r, g, b) = (
(if is_shaded { alt } else { alt } * if is_temperature { temperature as f64 } else if is_shaded { alt } else { 0.0 }).sqrt(),
if is_shaded { 0.2 + (alt * 0.8) } else { alt },
@ -170,7 +220,19 @@ fn main() {
255,
]) */
},
None | Some(RiverKind::Lake { .. }) => u32::from_le_bytes([
(Some(RiverKind::Ocean), _) => u32::from_le_bytes([
((64.0 - water_depth * 64.0) * 1.0) as u8,
((32.0 - water_depth * 32.0) * 1.0) as u8,
0,
255,
]),
(Some(RiverKind::River { .. }), _) => u32::from_le_bytes([
64 + (alt * 191.0) as u8,
32 + (alt * 95.0) as u8,
0,
255,
]),
(None, _) | (Some(RiverKind::Lake { .. }), _) => u32::from_le_bytes([
(((64.0 + water_alt * 191.0) + (- water_depth * 64.0)) * 1.0) as u8,
(((32.0 + water_alt * 95.0) + (- water_depth * 32.0)) * 1.0) as u8,
0,
@ -180,12 +242,14 @@ fn main() {
}
}
let spd = 32;
let spd = 32.0;
let lspd = 0.1;
if win.is_key_down(minifb::Key::P) {
println!(
"\
Gain: {:?}\n\
Scale: {:?}\n\
Gain / Shade gain: {:?} / {:?}\n\
Scale / Focus: {:?} / {:?}\n\
Light: {:?}
Land(adjacent): (X = temp, Y = humidity): {:?}\n\
Rivers: {:?}\n\
Lakes: {:?}\n\
@ -193,7 +257,10 @@ fn main() {
Total water: {:?}\n\
Total land(adjacent): {:?}",
gain,
lgain,
scale,
focus,
light_direction,
quads,
rivers,
lakes,
@ -204,13 +271,14 @@ fn main() {
}
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;
let pos = (focus_rect + (Vec2::new(mx as f64, my as f64) * scale)).map(|e| e as i32);
println!(
"Chunk position: {:?}",
pos.map2(TerrainChunkSize::RECT_SIZE, |e, f| e * f as i32)
);
}
}
let is_camera = win.is_key_down(minifb::Key::C);
if win.is_key_down(minifb::Key::B) {
is_basement ^= true;
}
@ -220,32 +288,77 @@ fn main() {
if win.is_key_down(minifb::Key::T) {
is_temperature ^= true;
}
if win.is_key_down(minifb::Key::O) {
is_water ^= true;
}
if win.is_key_down(minifb::Key::L) {
is_shaded ^= true;
}
if win.is_key_down(minifb::Key::W) {
focus.y -= spd * scale;
if is_camera {
light_direction.z -= lspd;
} else {
focus.y -= spd * scale;
}
}
if win.is_key_down(minifb::Key::A) {
focus.x -= spd * scale;
if is_camera {
light_direction.x -= lspd;
} else {
focus.x -= spd * scale;
}
}
if win.is_key_down(minifb::Key::S) {
focus.y += spd * scale;
if is_camera {
light_direction.z += lspd;
} else {
focus.y += spd * scale;
}
}
if win.is_key_down(minifb::Key::D) {
focus.x += spd * scale;
if is_camera {
light_direction.x += lspd;
} else {
focus.x += spd * scale;
}
}
if win.is_key_down(minifb::Key::Q) {
gain += 64.0;
if is_camera {
if (lgain * 2.0).is_normal() {
lgain *= 2.0;
}
} else {
gain += 64.0;
}
}
if win.is_key_down(minifb::Key::E) {
gain = (gain - 64.0).max(64.0);
if is_camera {
if (lgain / 2.0).is_normal() {
lgain /= 2.0;
}
} else {
gain = (gain - 64.0).max(64.0);
}
}
if win.is_key_down(minifb::Key::R) {
scale += 1;
if is_camera {
focus.z += spd * scale;
} else {
if (scale * 2.0).is_normal() {
scale *= 2.0;
}
// scale += 1;
}
}
if win.is_key_down(minifb::Key::F) {
scale = (scale - 1).max(0);
if is_camera {
focus.z -= spd * scale;
} else {
if (scale / 2.0).is_normal() {
scale /= 2.0;
}
// scale = (scale - 1).max(0);
}
}
win.update_with_buffer(&buf).unwrap();

View File

@ -149,6 +149,7 @@ impl<'a> BlockGen<'a> {
let sample = &z_cache?.sample;
let &ColumnSample {
alt,
basement,
chaos,
water_level,
warp_factor,
@ -177,10 +178,10 @@ impl<'a> BlockGen<'a> {
let wposf = wpos.map(|e| e as f64);
let (block, height) = if !only_structures {
let (_definitely_underground, height, on_cliff, water_height) =
let (_definitely_underground, height, on_cliff, basement_height, water_height) =
if (wposf.z as f32) < alt - 64.0 * chaos {
// Shortcut warping
(true, alt, false, water_level)
(true, alt, false, basement, water_level)
} else {
// Apply warping
let warp = world
@ -223,6 +224,7 @@ impl<'a> BlockGen<'a> {
false,
height,
on_cliff,
basement.min(basement + warp),
(if water_level <= alt {
water_level + warp
} else {
@ -247,10 +249,11 @@ impl<'a> BlockGen<'a> {
let water = Block::new(BlockKind::Water, Rgb::new(60, 90, 190));
let grass_depth = 1.5 + 2.0 * chaos;
let grass_depth = (1.5 + 2.0 * chaos).min(height - basement_height);
let block = if (wposf.z as f32) < height - grass_depth {
let col = Lerp::lerp(
saturate_srgb(sub_surface_color, 0.45).map(|e| (e * 255.0) as u8),
// saturate_srgb(sub_surface_color, 0.45).map(|e| (e * 255.0) as u8),
sub_surface_color.map(|e| (e * 255.0) as u8),
stone_col,
(height - grass_depth - wposf.z as f32) * 0.15,
);
@ -272,7 +275,8 @@ impl<'a> BlockGen<'a> {
// Surface
Some(Block::new(
BlockKind::Normal,
saturate_srgb(col, 0.45).map(|e| (e * 255.0) as u8),
// saturate_srgb(col, 0.45).map(|e| (e * 255.0) as u8),
col.map(|e| (e * 255.0) as u8),
))
} else if (wposf.z as f32) < height + 0.9
&& temp < CONFIG.desert_temp

View File

@ -443,7 +443,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
sim.get_interpolated_monotone(wpos, |chunk| chunk.alt)?
// sim.get_interpolated(wpos, |chunk| chunk.alt)?
};
let basement = sim./*get_interpolated*/get_interpolated_monotone(wpos, |chunk| chunk.basement)?;
let basement = alt + sim./*get_interpolated*/get_interpolated_monotone(wpos, |chunk| chunk.basement.sub(chunk.alt))?;
// Find the average distance to each neighboring body of water.
let mut river_count = 0.0f64;
@ -755,7 +755,8 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
lake_dist <= TerrainChunkSize::RECT_SIZE.x as f64 * 0.5;
if gouge_factor == 1.0 {
return Some((
alt_for_river < lake_water_alt || in_bounds,
/*alt_for_river < lake_water_alt || in_bounds,*/
true,
alt.min(lake_water_alt - 1.0 - river_gouge),
downhill_water_alt.max(lake_water_alt)
- river_gouge,
@ -763,7 +764,8 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
));
} else {
return Some((
alt_for_river < lake_water_alt || in_bounds,
/*alt_for_river < lake_water_alt || in_bounds,*/
true,
alt_for_river,
if in_bounds_ {
downhill_water_alt.max(lake_water_alt)
@ -804,6 +806,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
} else {
(false, alt_for_river, downhill_water_alt, 1.0)
};
let warp_factor = 0.0;
let riverless_alt_delta = (sim
.gen_ctx
@ -867,6 +870,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
// let wet_grass = Rgb::new(0.1, 0.5, 0.5);
let cold_stone = Rgb::new(0.57, 0.67, 0.8);
// let cold_stone = Rgb::new(0.5, 0.5, 0.5);
let hot_stone = Rgb::new(0.07, 0.07, 0.06);
let warm_stone = Rgb::new(0.77, 0.77, 0.64);
// //let warm_stone = Rgb::new(0.6, 0.6, 0.5);
// let warm_stone = Rgb::new(0.6, 0.5, 0.1);
@ -890,7 +894,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
);
let tundra = Lerp::lerp(snow, Rgb::new(0.01, 0.3, 0.0), 0.4 + marble * 0.6);
let dead_tundra = Lerp::lerp(warm_stone, Rgb::new(0.3, 0.12, 0.2), marble);
let cliff = Rgb::lerp(cold_stone, warm_stone, marble);
let cliff = Rgb::lerp(cold_stone, /*warm_stone*/hot_stone, marble);
let grass = Rgb::lerp(
cold_grass,
@ -918,7 +922,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
// For below desert humidity, we are always sand or rock, depending on altitude and
// temperature.
let ground = Rgb::lerp(
/* let ground = Rgb::lerp(
cliff,
Rgb::lerp(
dead_tundra,
@ -932,7 +936,39 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
/* alt.sub(CONFIG.sea_level)
.sub(CONFIG.mountain_scale * 0.25)
.div(CONFIG.mountain_scale * 0.125), */
); */
let ground = Lerp::lerp(
Lerp::lerp(
dead_tundra,
sand,
temp.sub(CONFIG.snow_temp)
.div(CONFIG.desert_temp.sub(CONFIG.snow_temp))
.mul(/*4.5*/ 0.5),
),
dirt,
humidity
.sub(CONFIG.desert_hum)
.div(CONFIG.forest_hum.sub(CONFIG.desert_hum))
.mul(1.0),
);
let sub_surface_color = Lerp::lerp(
cliff,
ground,
alt.sub(basement).mul(0.25)
);
/* let ground = Rgb::lerp(
dead_tundra,
sand,
temp.sub(CONFIG.snow_temp)
.div(CONFIG.desert_temp.sub(CONFIG.snow_temp))
.mul(/*4.5*/ 0.5),
/* 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,
// depending on temperature.
let ground = Rgb::lerp(
@ -942,18 +978,20 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
Rgb::lerp(
Rgb::lerp(
tundra,
// snow_temp to 0
// snow_temp to temperate_temp
dirt,
temp.sub(CONFIG.snow_temp)
.div(CONFIG.snow_temp.neg())
.div(CONFIG.temperate_temp.sub(CONFIG.snow_temp))
/*.sub((marble - 0.5) * 0.05)
.mul(256.0)*/
.mul(1.0),
// .mul(2.0),
),
// 0 to tropical_temp
// temperate_temp to tropical_temp
grass,
temp.div(CONFIG.tropical_temp).mul(4.0),
temp.sub(CONFIG.temperate_temp)
.div(CONFIG.tropical_temp.sub(CONFIG.temperate_temp))
.mul(4.0),
),
// tropical_temp to desert_temp
moss,
@ -983,9 +1021,11 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
Rgb::lerp(
Rgb::lerp(
snow_moss,
// 0 to tropical_temp
// temperate_temp to tropical_temp
grass,
temp.div(CONFIG.tropical_temp).mul(4.0),
temp.sub(CONFIG.temperate_temp)
.div(CONFIG.tropical_temp.sub(CONFIG.temperate_temp))
.mul(4.0),
),
// tropical_temp to desert_temp
tropical,
@ -1014,9 +1054,11 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
Rgb::lerp(
Rgb::lerp(
snow_moss,
// 0 to tropical_temp
// temperate_temp to tropical_temp
rainforest,
temp.div(CONFIG.tropical_temp).mul(4.0),
temp.sub(CONFIG.temperate_temp)
.div(CONFIG.tropical_temp.sub(CONFIG.temperate_temp))
.mul(4.0),
),
// tropical_temp to desert_temp
tropical,
@ -1035,15 +1077,35 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
humidity.sub(CONFIG.jungle_hum).mul(1.0),
);
// Snow covering
/* // Bedrock
let ground = Rgb::lerp(
cliff,
ground,
alt.sub(basement)
.mul(0.25)
); */
// Snow covering
let snow_cover =
temp.sub(CONFIG.snow_temp)
.max(-humidity.sub(CONFIG.desert_hum))
.mul(16.0)
.add((marble_small - 0.5) * 0.5);
let (alt, ground, sub_surface_color) = if snow_cover /*< 0.1*/<= 0.5 && alt > water_level {
// Allow snow cover.
(alt + 1.0 - snow_cover.max(0.0), Rgb::lerp(snow, ground, snow_cover),
Lerp::lerp(sub_surface_color, ground, alt.sub(basement).mul(0.15)))
} else {
(alt, ground, sub_surface_color)
};
/* let ground = Rgb::lerp(
snow,
ground,
temp.sub(CONFIG.snow_temp)
.max(-humidity.sub(CONFIG.desert_hum))
.mul(16.0)
.add((marble_small - 0.5) * 0.5),
);
); */
// Caves
let cave_at = |wposf: Vec2<f64>| {
@ -1091,6 +1153,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
Some(ColumnSample {
alt,
basement,
chaos,
water_level,
warp_factor,
@ -1101,9 +1164,9 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
// Beach
((ocean_level - 1.0) / 2.0).max(0.0),
),
sub_surface_color: /*warm_grass*/Lerp::lerp(cliff, dirt, alt.sub(basement).mul(0.25)),
sub_surface_color,// /*warm_grass*/Lerp::lerp(cliff, dirt, alt.sub(basement).mul(0.25)),
// No growing directly on bedrock.
tree_density: if alt - basement < 2.0 { 0.0 } else { tree_density },
tree_density: Lerp::lerp(0.0, tree_density, alt.sub(2.0).sub(basement).mul(0.5)),
forest_kind: sim_chunk.forest_kind,
close_structures: self.gen_close_structures(wpos),
cave_xy,
@ -1139,6 +1202,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
#[derive(Clone)]
pub struct ColumnSample<'a> {
pub alt: f32,
pub basement: f32,
pub chaos: f32,
pub water_level: f32,
pub warp_factor: f32,

View File

@ -2,6 +2,7 @@ pub struct Config {
pub sea_level: f32,
pub mountain_scale: f32,
pub snow_temp: f32,
pub temperate_temp: f32,
pub tropical_temp: f32,
pub desert_temp: f32,
pub desert_hum: f32,
@ -46,9 +47,10 @@ pub struct Config {
pub const CONFIG: Config = Config {
sea_level: 140.0,
mountain_scale: 2048.0,
snow_temp: -0.6,
tropical_temp: 0.2,
desert_temp: 0.6,
snow_temp: -0.8,
temperate_temp: -0.4,
tropical_temp: 0.4,
desert_temp: 0.8,
desert_hum: 0.15,
forest_hum: 0.5,
jungle_hum: 0.85,

View File

@ -36,9 +36,9 @@ pub struct World {
}
impl World {
pub fn generate(seed: u32) -> Self {
pub fn generate(seed: u32, opts: sim::WorldOpts) -> Self {
Self {
sim: sim::WorldSim::generate(seed),
sim: sim::WorldSim::generate(seed, opts),
}
}

View File

@ -140,10 +140,10 @@ pub fn diffusion(nx: usize, ny: usize, xl: f64, yl: f64, dt: f64, _ibc: (),
enddo
*/
for i in 1..nx-1 {
factxp = (kdint[aij(i+1, j)] + kdint[aij(i,j)]) / 2.0 * (dt / 2.0) / (dx * dx);
factxm = (kdint[aij(i-1, j)] + kdint[aij(i,j)]) / 2.0 * (dt / 2.0) / (dx * dx);
factyp = (kdint[aij(i, j+1)] + kdint[aij(i,j)]) / 2.0 * (dt / 2.0) / (dy * dy);
factym = (kdint[aij(i, j-1)] + kdint[aij(i,j)]) / 2.0 * (dt / 2.0) / (dy * dy);
factxp = (kdint[aij(i+1, j)] + kdint[aij(i, j)]) / 2.0 * (dt / 2.0) / (dx * dx);
factxm = (kdint[aij(i-1, j)] + kdint[aij(i, j)]) / 2.0 * (dt / 2.0) / (dx * dx);
factyp = (kdint[aij(i, j+1)] + kdint[aij(i, j)]) / 2.0 * (dt / 2.0) / (dy * dy);
factym = (kdint[aij(i, j-1)] + kdint[aij(i, j)]) / 2.0 * (dt / 2.0) / (dy * dy);
diag[i] = 1.0 + factxp + factxm;
sup[i] = -factxp;
inf[i] = -factxm;
@ -167,12 +167,21 @@ pub fn diffusion(nx: usize, ny: usize, xl: f64, yl: f64, dt: f64, _ibc: (),
f(1)=zintp(1,j)+factyp*zintp(1,j+1)-(factyp+factym)*zintp(1,j)+factym*zintp(1,j-1)
endif
*/
if true {
if false {
diag[0] = 1.0;
sup[0] = 0.0;
f[0] = zintp[aij(0, j)];
} else {
// FIXME: implement reflective boundary
// reflective boundary
factxp = (kdint[aij(1, j)] + kdint[aij(0, j)]) / 2.0 * (dt / 2.0) / (dx * dx);
factyp = (kdint[aij(0, j+1)] + kdint[aij(0, j)]) / 2.0 * (dt / 2.0) / (dy * dy);
factym = (kdint[aij(0, j-1)] + kdint[aij(0, j)]) / 2.0 * (dt / 2.0) / (dy * dy);
diag[0] = 1.0 + factxp;
sup[0] = -factxp;
f[0] =
zintp[aij(0, j)] + factyp * zintp[aij(0, j+1)] -
(factyp + factym) *
zintp[aij(0, j)] + factym * zintp[aij(0, j-1)];
}
/*
! right bc
@ -189,12 +198,21 @@ pub fn diffusion(nx: usize, ny: usize, xl: f64, yl: f64, dt: f64, _ibc: (),
f(nx)=zintp(nx,j)+factyp*zintp(nx,j+1)-(factyp+factym)*zintp(nx,j)+factym*zintp(nx,j-1)
endif
*/
if true {
if false {
diag[nx - 1] = 1.0;
inf[nx - 1] = 0.0;
f[nx - 1] = zintp[aij(nx - 1, j)];
} else {
// FIXME: implement reflective boundary
// reflective boundary
factxm = (kdint[aij(nx-2, j)] + kdint[aij(nx-1, j)]) / 2.0 * (dt / 2.0) / (dx * dx);
factyp = (kdint[aij(nx-1, j+1)] + kdint[aij(nx-1, j)]) / 2.0 * (dt / 2.0) / (dy * dy);
factym = (kdint[aij(nx-1, j-1)] + kdint[aij(nx-1, j)]) / 2.0 * (dt / 2.0) / (dy * dy);
diag[nx - 1] = 1.0 + factxm;
inf[nx - 1] = -factxm;
f[nx - 1] =
zintp[aij(nx-1, j)] + factyp * zintp[aij(nx-1, j+1)] -
(factyp + factym) *
zintp[aij(nx-1, j)] + factym * zintp[aij(nx-1, j-1)];
}
/*
call tridag (inf,diag,sup,f,res,nx)
@ -242,10 +260,10 @@ pub fn diffusion(nx: usize, ny: usize, xl: f64, yl: f64, dt: f64, _ibc: (),
enddo
*/
for j in 1..ny-1 {
factxp = (kdint[aij(i+1, j)] + kdint[aij(i,j)]) / 2.0 * (dt / 2.0) / (dx * dx);
factxm = (kdint[aij(i-1, j)] + kdint[aij(i,j)]) / 2.0 * (dt / 2.0) / (dx * dx);
factyp = (kdint[aij(i, j+1)] + kdint[aij(i,j)]) / 2.0 * (dt / 2.0) / (dy * dy);
factym = (kdint[aij(i, j-1)] + kdint[aij(i,j)]) / 2.0 * (dt / 2.0) / (dy * dy);
factxp = (kdint[aij(i+1, j)] + kdint[aij(i, j)]) / 2.0 * (dt / 2.0) / (dx * dx);
factxm = (kdint[aij(i-1, j)] + kdint[aij(i, j)]) / 2.0 * (dt / 2.0) / (dx * dx);
factyp = (kdint[aij(i, j+1)] + kdint[aij(i, j)]) / 2.0 * (dt / 2.0) / (dy * dy);
factym = (kdint[aij(i, j-1)] + kdint[aij(i, j)]) / 2.0 * (dt / 2.0) / (dy * dy);
diag[j] = 1.0 + factyp + factym;
sup[j] = -factyp;
inf[j] = -factym;
@ -274,7 +292,19 @@ pub fn diffusion(nx: usize, ny: usize, xl: f64, yl: f64, dt: f64, _ibc: (),
sup[0] = 0.0;
f[0] = zint[aij(i, 0)];
} else {
// FIXME: implement reflective boundary
// reflective boundary
// TODO: Check whether this j was actually supposed to be a 0 in the original
// (probably).
// factxp = (kdint[aij(i+1, 0)] + kdint[aij(i, j)]) / 2.0 * (dt / 2.0) / (dx * dx);
factxp = (kdint[aij(i+1, 0)] + kdint[aij(i, 0)]) / 2.0 * (dt / 2.0) / (dx * dx);
factxm = (kdint[aij(i-1, 0)] + kdint[aij(i, 0)]) / 2.0 * (dt / 2.0) / (dx * dx);
factyp = (kdint[aij(i, 1)] + kdint[aij(i, 0)]) / 2.0 * (dt / 2.0) / (dy * dy);
diag[0] = 1.0 + factyp;
sup[0] = -factyp;
f[0] =
zint[aij(i, 0)] + factxp * zint[aij(i+1, 0)] -
(factxp + factxm) *
zint[aij(i, 0)] + factxm * zint[aij(i-1, 0)];
}
/*
! top bc
@ -296,7 +326,16 @@ pub fn diffusion(nx: usize, ny: usize, xl: f64, yl: f64, dt: f64, _ibc: (),
inf[ny - 1] = 0.0;
f[ny - 1] = zint[aij(i, ny - 1)];
} else {
// FIXME: implement reflective boundary
// reflective boundary
factxp = (kdint[aij(i+1, ny-1)] + kdint[aij(i, ny-1)]) / 2.0 * (dt / 2.0) / (dx * dx);
factxm = (kdint[aij(i-1, ny-1)] + kdint[aij(i, ny-1)]) / 2.0 * (dt / 2.0) / (dx * dx);
factym = (kdint[aij(i, ny-2)] + kdint[aij(i, ny-1)]) / 2.0 * (dt / 2.0) / (dy * dy);
diag[ny - 1] = 1.0 + factym;
inf[ny - 1] = -factym;
f[ny - 1] =
zint[aij(i, ny-1)] + factxp * zint[aij(i+1, ny-1)] -
(factxp + factxm) *
zint[aij(i, ny-1)] + factxm * zint[aij(i-1, ny-1)];
}
/*
call tridag (inf,diag,sup,f,res,ny)
@ -331,7 +370,7 @@ pub fn diffusion(nx: usize, ny: usize, xl: f64, yl: f64, dt: f64, _ibc: (),
for i in 0..nx {
ij = aij(i, j);
// FIXME: Track total erosion and erosion rate.
h[aij(i, j)] = zintp[aij(i, j)] as f32;
h[ij] = zintp[ij] as f32;
}
}
@ -399,6 +438,13 @@ do 11 j=2,n
gam[j] = c[j - 1] / bet;
bet = b[j] - a[j] * gam[j];
assert!(bet != 0.0);
// Round 0: u[0] = r[0] / b[0]
// = r'[0] / b'[0]
// Round j: u[j] = (r[j] - a[j] * u'[j - 1]) / b'[j]
// = (r[j] - a[j] * r'[j - 1] / b'[j - 1]) / b'[j]
// = (r[j] - (a[j] / b'[j - 1]) * r'[j - 1]) / b'[j]
// = (r[j] - w[j] * r'[j - 1]) / b'[j]
// = r'[j] / b'[j]
u[j] = (r[j] - a[j] * u[j - 1]) / bet;
}
/*

View File

@ -487,8 +487,8 @@ pub fn get_rivers(
///
/// TODO: See if allocating in advance is worthwhile.
fn get_max_slope(h: &[f32], rock_strength_nz: &(impl NoiseFn<Point3<f64>> + Sync)) -> Box<[f64]> {
const MIN_MAX_ANGLE: f64 = 6.0/*30.0*//*6.0*//*15.0*/ / 360.0 * 2.0 * f64::consts::PI;
const MAX_MAX_ANGLE: f64 = 54.0/*50.0*//*54.0*//*45.0*/ / 360.0 * 2.0 * f64::consts::PI;
const MIN_MAX_ANGLE: f64 = 15.0/*6.0*//*30.0*//*6.0*//*15.0*/ / 360.0 * 2.0 * f64::consts::PI;
const MAX_MAX_ANGLE: f64 = 60.0/*54.0*//*50.0*//*54.0*//*45.0*/ / 360.0 * 2.0 * f64::consts::PI;
const MAX_ANGLE_RANGE: f64 = MAX_MAX_ANGLE - MIN_MAX_ANGLE;
let height_scale = 1.0; // 1.0 / CONFIG.mountain_scale as f64;
h.par_iter()
@ -497,7 +497,7 @@ fn get_max_slope(h: &[f32], rock_strength_nz: &(impl NoiseFn<Point3<f64>> + Sync
let wposf = uniform_idx_as_vec2(posi).map(|e| e as f64) * TerrainChunkSize::RECT_SIZE.map(|e| e as f64);
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/*1.0*//*4.0*/ * /*1.0*/4.0/* TerrainChunkSize::RECT_SIZE.x as f64 / 8.0 */;
let div_factor = /*32.0*//*16.0*//*64.0*//*256.0*/8.0/*8.0*//*1.0*//*4.0*/ * /*1.0*/16.0/* TerrainChunkSize::RECT_SIZE.x as f64 / 8.0 */;
let rock_strength = rock_strength_nz
.get([
wposf.x, /* / div_factor*/
@ -531,9 +531,9 @@ fn get_max_slope(h: &[f32], rock_strength_nz: &(impl NoiseFn<Point3<f64>> + Sync
// TODO: Make all this stuff configurable... but honestly, it's so complicated that I'm not
// sure anyone would be able to usefully tweak them on a per-map basis? Plus it's just a
// hacky heuristic anyway.
let center = 0.25;
let center = /*0.25*/0.4;
let dmin = center - /*0.15;//0.05*/0.05;
let dmax = center + 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 rock_strength = logistic_cdf(
1.0 * logit(rock_strength.min(1.0f64 - 1e-7).max(1e-7))
@ -541,6 +541,7 @@ fn get_max_slope(h: &[f32], rock_strength_nz: &(impl NoiseFn<Point3<f64>> + Sync
);
// let rock_strength = 0.5;
let max_slope = (rock_strength * MAX_ANGLE_RANGE + MIN_MAX_ANGLE).tan();
// let max_slope = /*30.0.to_radians().tan();*/3.0.sqrt() / 3.0;
max_slope
})
.collect::<Vec<_>>()
@ -554,7 +555,7 @@ fn get_max_slope(h: &[f32], rock_strength_nz: &(impl NoiseFn<Point3<f64>> + Sync
/// dh(p) / dt = uplift(p)k * A(p)^m * slope(p)^n
///
/// where A(p) is the drainage area at p, m and n are constants
/// (we choose m = 0.5 and n = 1), and k is a constant. We choose
/// (we choose m = 0.4 and n = 1), and k is a constant. We choose
///
/// k = 2.244 * uplift.max() / (desired_max_height)
///
@ -594,6 +595,10 @@ fn get_max_slope(h: &[f32], rock_strength_nz: &(impl NoiseFn<Point3<f64>> + Sync
///
/// https://github.com/fastscape-lem/fastscapelib-fortran/blob/master/src/Diffusion.f90
///
/// We also borrow the implementation for sediment transport from
///
/// https://github.com/fastscape-lem/fastscapelib-fortran/blob/master/src/StreamPowerLaw.f90
///
/// [1] Guillaume Cordonnier, Jean Braun, Marie-Paule Cani, Bedrich Benes, Eric Galin, et al..
/// Large Scale Terrain Generation from Tectonic Uplift and Fluvial Erosion.
/// Computer Graphics Forum, Wiley, 2016, Proc. EUROGRAPHICS 2016, 35 (2), pp.165-175.
@ -608,11 +613,14 @@ fn erode(
done_val: bool,
erosion_base: f32,
max_uplift: f32,
max_g: f32,
kdsed: f64,
_seed: &RandomField,
rock_strength_nz: &(impl NoiseFn<Point3<f64>> + Sync),
uplift: impl Fn(usize) -> f32,
kd: impl Fn(usize) -> f64,
uplift: impl Fn(usize) -> f32 + Sync,
kf: impl Fn(usize) -> f32,
kd: impl Fn(usize) -> f32,
g: impl Fn(usize) -> f32 + Sync,
is_ocean: impl Fn(usize) -> bool + Sync,
) {
log::debug!("Done draining...");
@ -636,12 +644,14 @@ fn erode(
//
// max_uplift / height_scale m / dt / (5.010e-4 m / y) = 1
// (max_uplift / height_scale / 5.010e-4) y = dt
let dt = max_uplift as f64 / height_scale /* * CONFIG.mountain_scale as f64*/ / 5.010e-4;
// 5e-7
let dt = max_uplift as f64 / height_scale /* * CONFIG.mountain_scale as f64*/ / /*5.010e-4*/1e-3;
println!("dt={:?}", 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 * dt;
// Net precipitation rate (m / year)
let p = 1.0 * height_scale;
let m = 0.4;
// Stream power erosion constant (bedrock), in m^(1-2m) / year (times dt).
let k_fb = // erosion_base as f64 + 2.244 / mmaxh as f64 * max_uplift as f64;
// 2.244*(5.010e-4)/512*5- (1.097e-5)
@ -653,28 +663,40 @@ fn erode(
// 2e-5 * dt;
// 2.244/2048*5*32/(250000/4)*10^6
// ln(tan(30/360*2*pi))-ln(tan(6/360*2*pi))*1500 = 3378
erosion_base as f64 + 2.244 / mmaxh as f64 * /*10.0*//*5.0*//*9.0*//*7.5*//*5.0*//*2.5*/1.5/*3.75*/ * max_uplift as f64;
// 1e-5 * dt;
//erosion_base as f64 + 2.244 / mmaxh as f64 * /*10.0*//*5.0*//*9.0*//*7.5*//*5.0*//*2.5*//*1.5*//*5.0*//*1.0*//*1.5*//*2.5*//*3.75*/ * max_uplift as f64;
// 2.5e-6 * dt;
2e-5 * dt;
// see http://geosci.uchicago.edu/~kite/doc/Whipple_and_Tucker_1999.pdf
//5e-6 * dt; // 2e-5 was designed for steady state uplift of 2mm / y whih would amount to 500 m / 250,000 y.
// (2.244*(5.010e-4)/512)/(2.244*(5.010e-4)/2500) = 4.88...
// 2.444 * 5
// Stream power erosion constant (sediment), in m^(1-2m) / year (times dt).
let k_fs = k_fb * /*1.0*//*2.0*/2.0/*4.0*/;
let k_fs_mult = 2.0;/*1.5*/;
// let k_fs = k_fb * 1.0/*1.5*//*2.0*//*2.0*//*4.0*/;
// u = k * h_max / 2.244
// let uplift_scale = erosion_base as f64 + (k_fb * mmaxh / 2.244 / 5.010e-4 as f64 * mmaxh as f64) * dt;
let ((dh, indirection, newh, area), mut max_slopes) = rayon::join(
let ((dh, indirection, newh, maxh, area), (mut max_slopes, ht)) = rayon::join(
|| {
let mut dh = downhill(h, |posi| is_ocean(posi));
let mut dh = downhill(h, |posi| is_ocean(posi) && h[posi] <= 0.0);
log::debug!("Computed downhill...");
let (boundary_len, indirection, newh) = get_lakes(&h, &mut dh);
let (boundary_len, indirection, newh, maxh) = get_lakes(&h, &mut dh);
log::debug!("Got lakes...");
let area = get_drainage(&newh, &dh, boundary_len);
log::debug!("Got flux...");
(dh, indirection, newh, area)
(dh, indirection, newh, maxh, area)
},
|| {
let max_slope = get_max_slope(h, rock_strength_nz);
log::debug!("Got max slopes...");
max_slope
rayon::join(
|| {
let max_slope = get_max_slope(h, rock_strength_nz);
log::debug!("Got max slopes...");
max_slope
},
|| {
// Store the elevation at t
h.to_vec().into_boxed_slice()
},
)
},
);
@ -683,140 +705,297 @@ fn erode(
let neighbor_coef = TerrainChunkSize::RECT_SIZE.map(|e| e as f64);
let chunk_area = neighbor_coef.x * neighbor_coef.y;
// TODO: Make more principled.
let mid_slope = (30.0 / 360.0 * 2.0 * f64::consts::PI).tan();
// Iterate in ascending height order.
let mid_slope = (30.0 / 360.0 * 2.0 * f64::consts::PI).tan();//1.0;
let mut lake_water_volume = vec![/*-1i32*/0.0f64; WORLD_SIZE.x * WORLD_SIZE.y].into_boxed_slice();
let mut elev = vec![/*-1i32*/0.0f64; WORLD_SIZE.x * WORLD_SIZE.y].into_boxed_slice();
let mut hp = vec![/*-1i32*/0.0f64; WORLD_SIZE.x * WORLD_SIZE.y].into_boxed_slice();
let mut deltah = vec![/*-1i32*/0.0f64; WORLD_SIZE.x * WORLD_SIZE.y].into_boxed_slice();
// calculate the elevation / SPL, including sediment flux
let tol = 1.0e-4f64 * (maxh as f64 + 1.0);
let mut err = 2.0 * tol;
// Some variables for tracking statistics, currently only for debugging purposes.
let mut maxh = 0.0;
let mut nland = 0usize;
let mut sums = 0.0;
let mut sumh = 0.0;
for &posi in &*newh {
let posi = posi as usize;
let posj = dh[posi];
if posj < 0 {
if posj == -1 {
panic!("Disconnected lake!");
}
// wh for oceans is always 0.
wh[posi] = 0.0;
// max_slopes[posi] = kd(posi);
// Egress with no outgoing flows.
} else {
// *is_done.at(posi) = done_val;
let posj = posj as usize;
let dxy = (uniform_idx_as_vec2(posi) - uniform_idx_as_vec2(posj)).map(|e| e as f64);
let mut ntherm = 0usize;
// Has an outgoing flow edge (posi, posj).
// flux(i) = k * A[i]^m / ((p(i) - p(j)).magnitude()), and δt = 1
let neighbor_distance = (neighbor_coef * dxy).magnitude();
// Since the area is in meters^(2m) and neighbor_distance is in m, so long as m = 0.5,
// we have meters^(1) / meters^(1), so they should cancel out. Otherwise, we would
// want to multiply neighbor_distance by height_scale and area[posi] by
// height_scale^2, to make sure we were working in the correct units for dz
// (which has height_scale height unit = 1.0 meters).
let old_h_i = h[posi] as f64;
let old_b_i = b[posi] as f64;
let uplift_i = uplift(posi) as f64;
let k = if (old_h_i - old_b_i as f64) > sediment_thickness {
// Sediment
k_fs
// Gauss-Seidel iteration
let mut lake_sediment = vec![/*-1i32*/0.0f64; WORLD_SIZE.x * WORLD_SIZE.y].into_boxed_slice();
let mut lake_sill = vec![/*-1i32*/-1isize; WORLD_SIZE.x * WORLD_SIZE.y].into_boxed_slice();
let mut n_gs_stream_power_law = 0;
while err > tol && n_gs_stream_power_law < 99 {
log::info!("Stream power iteration #{:?}", n_gs_stream_power_law);
// Reset statistics in each loop.
maxh = 0.0;
nland = 0usize;
sums = 0.0;
sumh = 0.0;
ntherm = 0usize;
// Keep track of how many iterations we've gone to test for convergence.
n_gs_stream_power_law += 1;
rayon::join(
|| {
// guess/update the elevation at t+Δt (k)
hp.par_iter_mut().zip(h.par_iter()).for_each(|(mut hp, h)| {
*hp = *h as f64;
});
},
|| {
// calculate erosion/deposition at each node
deltah.par_iter_mut().enumerate().for_each(|(posi, mut deltah)| {
*deltah = (ht[posi] - h[posi]) as f64;
});
},
);
// sum the erosion in stack order
for &posi in newh.iter().rev() {
let posi = posi as usize;
let posj = dh[posi];
if posj < 0 {
lake_sediment[posi] = deltah[posi];
} else {
// Bedrock
k_fb
};
// let k = k * uplift_i / max_uplift as f64;
let flux = k * (p * chunk_area * area[posi] as f64).sqrt() / neighbor_distance;
// 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 wh_j = wh[posj] as f64;
// hi(t + ∂t) = (hi(t) + ∂t(ui + kp^mAi^m(hj(t + ∂t)/||pi - pj||))) / (1 + ∂t * kp^mAi^m / ||pi - pj||)
let mut new_h_i = old_h_i + uplift_i;
// Only perform erosion if we are above the water level of the previous node.
if new_h_i > wh_j {
new_h_i = (new_h_i + flux * h_j) / (1.0 + flux);
// If we dipped below the receiver's water level, set our height to the receiver's
// water level.
if new_h_i <= wh_j {
new_h_i = wh_j;
} else {
let dz = (new_h_i - /*h_j*//*h_k*/wh_j).max(0.0) / height_scale/* * CONFIG.mountain_scale as f64*/;
let mag_slope = dz/*.abs()*/ / neighbor_distance;
nland += 1;
sumh += new_h_i;
sums += mag_slope;
let posj = posj as usize;
deltah[posi] -= (ht[posi] as f64 - hp[posi]);
let lposi = lake_sill[posi];
if lposi == posi as isize {
if deltah[posi] <= 0.0 {
lake_sediment[posi] = 0.0;
} else {
lake_sediment[posi] = deltah[posi];
}
}
}
// Set max_slope to this node's water height (max of receiver's water height and
// this node's height).
wh[posi] = wh_j.max(new_h_i) as f32;
// Prevent erosion from dropping us below our receiver, unless we were already below it.
// new_h_i = h_j.min(old_h_i + uplift_i).max(new_h_i);
// Find out if this is a lake bottom.
/* let indirection_idx = indirection[posi];
let is_lake_bottom = indirection_idx < 0;
let _fake_neighbor = is_lake_bottom && dxy.x.abs() > 1.0 && dxy.y.abs() > 1.0;
// Test the slope.
let max_slope = max_slopes[posi] as f64;
// Hacky version of thermal erosion: only consider lowest neighbor, don't redistribute
// uplift to other neighbors.
let (posk, h_k) = /* neighbors(posi)
.filter(|&posk| *is_done.at(posk) == done_val)
// .filter(|&posk| *is_done.at(posk) == done_val || is_ocean(posk))
.map(|posk| (posk, h[posk] as f64))
// .filter(|&(posk, h_k)| *is_done.at(posk) == done_val || h_k < 0.0)
.min_by(|&(_, a), &(_, b)| a.partial_cmp(&b).unwrap())
.unwrap_or((posj, h_j)); */
(posj, h_j);
// .max(h_j);
let (posk, h_k) = if h_k < h_j {
(posk, h_k)
} else {
(posj, h_j)
};
let dxy = (uniform_idx_as_vec2(posi) - uniform_idx_as_vec2(posk)).map(|e| e as f64);
let neighbor_distance = (neighbor_coef * dxy).magnitude();
let dz = (new_h_i - /*h_j*/h_k).max(0.0) / height_scale/* * CONFIG.mountain_scale as f64*/;
let mag_slope = dz/*.abs()*/ / neighbor_distance; */
// If you're on the lake bottom and not right next to your neighbor, don't compute a
// slope.
if
/* !is_lake_bottom */ /* !fake_neighbor */
true {
/* if
/* !is_lake_bottom && */
mag_slope > max_slope {
// println!("old slope: {:?}, new slope: {:?}, dz: {:?}, h_j: {:?}, new_h_i: {:?}", mag_slope, max_slope, dz, h_j, new_h_i);
// Thermal erosion says this can't happen, so we reduce dh_i to make the slope
// exactly max_slope.
// max_slope = (old_h_i + dh - h_j) / height_scale/* * CONFIG.mountain_scale */ / NEIGHBOR_DISTANCE
// dh = max_slope * NEIGHBOR_DISTANCE * height_scale/* / CONFIG.mountain_scale */ + h_j - old_h_i.
let dh = max_slope * neighbor_distance * height_scale/* / CONFIG.mountain_scale as f64*/;
new_h_i = /*h_j.max*/(h_k + dh).max(new_h_i - l * (mag_slope - max_slope));
let dz = (new_h_i - /*h_j*/h_k).max(0.0) / height_scale/* * CONFIG.mountain_scale as f64*/;
let slope = dz/*.abs()*/ / neighbor_distance;
sums += slope;
/* max_slopes[posi] = /*(mag_slope - max_slope) * */kd(posi);
sums += mag_slope; */
// let slope = dz.signum() * max_slope;
// new_h_i = slope * neighbor_distance * height_scale /* / CONFIG.mountain_scale as f64 */ + h_j;
// sums += max_slope;
} else {
// max_slopes[posi] = 0.0;
sums += mag_slope;
// Just use the computed rate.
} */
h[posi] = new_h_i as f32;
// Make sure to update the basement as well!
b[posi] = (old_b_i + uplift_i).min(new_h_i) as f32;
deltah[posi] += ht[posi] as f64 - hp[posi];
deltah[posj] += deltah[posi];
}
}
// *is_done.at(posi) = done_val;
maxh = h[posi].max(maxh);
// do ij=nn,1,-1
// ijk=stack(ij)
// ijr=rec(ijk)
// if (ijr.ne.ijk) then
// dh(ijk)=dh(ijk)-(ht(ijk)-hp(ijk))
// if (lake_sill(ijk).eq.ijk) then
// if (dh(ijk).le.0.d0) then
// lake_sediment(ijk)=0.d0
// else
// lake_sediment(ijk)=dh(ijk)
// endif
// endif
// dh(ijk)=dh(ijk)+(ht(ijk)-hp(ijk))
// dh(ijr)=dh(ijr)+dh(ijk)
// else
// lake_sediment(ijk)=dh(ijk)
// endif
// enddo
elev.par_iter_mut().enumerate().for_each(|(posi, mut elev)| {
if dh[posi] < 0 {
*elev = ht[posi] as f64;
} else {
*elev = ht[posi] as f64 + (deltah[posi] - (ht[posi] as f64 - hp[posi])) * g(posi) as f64 / area[posi] as f64;
}
});
// Iterate in ascending height order.
let mut sum_err = 0.0f64;
for &posi in &*newh {
let posi = posi as usize;
let posj = dh[posi];
if posj < 0 {
if posj == -1 {
panic!("Disconnected lake!");
}
// wh for oceans is always 0.
wh[posi] = 0.0;
lake_sill[posi] = posi as isize;
lake_water_volume[posi] = 0.0;
// max_slopes[posi] = kd(posi);
// Egress with no outgoing flows.
} else {
// *is_done.at(posi) = done_val;
let posj = posj as usize;
let dxy = (uniform_idx_as_vec2(posi) - uniform_idx_as_vec2(posj)).map(|e| e as f64);
// Has an outgoing flow edge (posi, posj).
// flux(i) = k * A[i]^m / ((p(i) - p(j)).magnitude()), and δt = 1
let neighbor_distance = (neighbor_coef * dxy).magnitude();
// Since the area is in meters^(2m) and neighbor_distance is in m, so long as m = 0.5,
// we have meters^(1) / meters^(1), so they should cancel out. Otherwise, we would
// want to multiply neighbor_distance by height_scale and area[posi] by
// height_scale^2, to make sure we were working in the correct units for dz
// (which has height_scale height unit = 1.0 meters).
let old_h_i = /*h*/elev[posi] as f64;
let old_b_i = b[posi] as f64;
let uplift_i = uplift(posi) as f64;
assert!(uplift_i.is_normal() && uplift_i == 0.0 || uplift_i.is_positive());
// 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 wh_j = wh[posj] as f64;
let mut new_h_i = old_h_i + uplift_i;
// Only perform erosion if we are above the water level of the previous node.
if new_h_i > wh_j {
// hi(t + ∂t) = (hi(t) + ∂t(ui + kp^mAi^m(hj(t + ∂t)/||pi - pj||))) / (1 + ∂t * kp^mAi^m / ||pi - pj||)
let k = if (old_h_i - old_b_i as f64) > sediment_thickness {
// Sediment
// k_fs
k_fs_mult * kf(posi) as f64
} else {
// Bedrock
// k_fb
kf(posi) as f64
} * dt;
// let k = k * uplift_i / max_uplift as f64;
let flux = k * (p * chunk_area * area[posi] as f64).powf(m) / neighbor_distance;
assert!(flux.is_normal() && flux.is_positive());
new_h_i = (new_h_i + flux * h_j) / (1.0 + flux);
lake_sill[posi] = posi as isize;
lake_water_volume[posi] = 0.0;
// If we dipped below the receiver's water level, set our height to the receiver's
// water level.
if new_h_i <= wh_j {
new_h_i = wh_j;
} else {
let dz = (new_h_i - /*h_j*//*h_k*/wh_j).max(0.0) / height_scale/* * CONFIG.mountain_scale as f64*/;
let mag_slope = dz/*.abs()*/ / neighbor_distance;
nland += 1;
sumh += new_h_i;
sums += mag_slope;
}
} else {
let lposj = lake_sill[posj];
lake_sill[posi] = lposj;
if lposj >= 0 {
let lposj = lposj as usize;
lake_water_volume[lposj] += wh_j - new_h_i;
}
}
// Set max_slope to this node's water height (max of receiver's water height and
// this node's height).
wh[posi] = wh_j.max(new_h_i) as f32;
// Prevent erosion from dropping us below our receiver, unless we were already below it.
// new_h_i = h_j.min(old_h_i + uplift_i).max(new_h_i);
// Find out if this is a lake bottom.
/* let indirection_idx = indirection[posi];
let is_lake_bottom = indirection_idx < 0;
let _fake_neighbor = is_lake_bottom && dxy.x.abs() > 1.0 && dxy.y.abs() > 1.0;
// Test the slope.
let max_slope = max_slopes[posi] as f64;
// Hacky version of thermal erosion: only consider lowest neighbor, don't redistribute
// uplift to other neighbors.
let (posk, h_k) = /* neighbors(posi)
.filter(|&posk| *is_done.at(posk) == done_val)
// .filter(|&posk| *is_done.at(posk) == done_val || is_ocean(posk))
.map(|posk| (posk, h[posk] as f64))
// .filter(|&(posk, h_k)| *is_done.at(posk) == done_val || h_k < 0.0)
.min_by(|&(_, a), &(_, b)| a.partial_cmp(&b).unwrap())
.unwrap_or((posj, h_j)); */
(posj, h_j);
// .max(h_j);
let (posk, h_k) = if h_k < h_j {
(posk, h_k)
} else {
(posj, h_j)
};
let dxy = (uniform_idx_as_vec2(posi) - uniform_idx_as_vec2(posk)).map(|e| e as f64);
let neighbor_distance = (neighbor_coef * dxy).magnitude();
let dz = (new_h_i - /*h_j*/h_k).max(0.0) / height_scale/* * CONFIG.mountain_scale as f64*/;
let mag_slope = dz/*.abs()*/ / neighbor_distance; */
// If you're on the lake bottom and not right next to your neighbor, don't compute a
// slope.
if
/* !is_lake_bottom */ /* !fake_neighbor */
true {
/* if
/* !is_lake_bottom && */
mag_slope > max_slope {
// println!("old slope: {:?}, new slope: {:?}, dz: {:?}, h_j: {:?}, new_h_i: {:?}", mag_slope, max_slope, dz, h_j, new_h_i);
// Thermal erosion says this can't happen, so we reduce dh_i to make the slope
// exactly max_slope.
// max_slope = (old_h_i + dh - h_j) / height_scale/* * CONFIG.mountain_scale */ / NEIGHBOR_DISTANCE
// dh = max_slope * NEIGHBOR_DISTANCE * height_scale/* / CONFIG.mountain_scale */ + h_j - old_h_i.
let dh = max_slope * neighbor_distance * height_scale/* / CONFIG.mountain_scale as f64*/;
new_h_i = /*h_j.max*/(h_k + dh).max(new_h_i - l * (mag_slope - max_slope));
let dz = (new_h_i - /*h_j*/h_k).max(0.0) / height_scale/* * CONFIG.mountain_scale as f64*/;
let slope = dz/*.abs()*/ / neighbor_distance;
sums += slope;
/* max_slopes[posi] = /*(mag_slope - max_slope) * */kd(posi);
sums += mag_slope; */
// let slope = dz.signum() * max_slope;
// new_h_i = slope * neighbor_distance * height_scale /* / CONFIG.mountain_scale as f64 */ + h_j;
// sums += max_slope;
} else {
// max_slopes[posi] = 0.0;
sums += mag_slope;
// Just use the computed rate.
} */
h[posi] = new_h_i as f32;
// Make sure to update the basement as well!
// b[posi] = (old_b_i + uplift_i).min(new_h_i) as f32;
}
}
// *is_done.at(posi) = done_val;
maxh = h[posi].max(maxh);
// Add sum of squares of errors.
sum_err += (h[posi] as f64 - hp[posi]).powi(2);
}
err = (sum_err / newh.len() as f64).sqrt();
if max_g == 0.0 {
err = 0.0;
}
if n_gs_stream_power_law == 99 {
log::warn!("Beware: Gauss-Siedel scheme not convergent");
}
}
//b=min(h,b)
// update the basement
b.par_iter_mut().zip(h.par_iter()).enumerate().for_each(|(posi, (mut b, h))| {
let old_b_i = *b;
let uplift_i = uplift(posi);
*b = (old_b_i + uplift_i).min(*h);
});
// update the height to reflect sediment flux.
h.par_iter_mut().enumerate().for_each(|(posi, mut h)| {
let lposi = lake_sill[posi];
if lposi >= 0 {
let lposi = lposi as usize;
if lake_water_volume[lposi] > 0.0 {
// +max(0.d0,min(lake_sediment(lake_sill(ij)),lake_water_volume(lake_sill(ij))))/
// lake_water_volume(lake_sill(ij))*(water(ij)-h(ij))
*h +=
(0.0.max(lake_sediment[lposi].min(lake_water_volume[lposi])) /
lake_water_volume[lposi] *
(wh[posi] - *h) as f64) as f32;
}
}
});
// do ij=1,nn
// if (lake_sill(ij).ne.0) then
// if (lake_water_volume(lake_sill(ij)).gt.0.d0) h(ij)=h(ij) &
// +max(0.d0,min(lake_sediment(lake_sill(ij)),lake_water_volume(lake_sill(ij))))/ &
// lake_water_volume(lake_sill(ij))*(water(ij)-h(ij))
// endif
// enddo
log::info!(
"Done applying stream power (max height: {:?}) (avg height: {:?}) (avg slope: {:?})",
"Done applying stream power (max height: {:?}) (avg height: {:?}) (avg slope: {:?}) (num land: {:?}) (num thermal: {:?})",
maxh,
if nland == 0 {
f64::INFINITY
@ -828,30 +1007,38 @@ fn erode(
} else {
sums / nland as f64
},
nland,
ntherm,
);
// Apply thermal erosion.
maxh = 0.0;
sumh = 0.0;
sums = 0.0;
nland = 0usize;
ntherm = 0usize;
for &posi in &*newh {
let posi = posi as usize;
let posj = dh[posi];
let old_h_i = h[posi] as f64;
let old_h_i = /*h*/b[posi] as f64;
let old_b_i = b[posi] as f64;
let max_slope = max_slopes[posi] as f64;
// Remember k_d for this chunk in max_slopes.
max_slopes[posi] = if (old_h_i - old_b_i as f64) > sediment_thickness {
let kd_factor =
// 1.0;
(1.0 / (max_slope / mid_slope/*.sqrt()*//*.powf(0.03125)*/).powf(/*2.0*/1.0))/*.min(kdsed)*/;
max_slopes[posi] = if (old_h_i - old_b_i as f64) > sediment_thickness && kdsed > 0.0 {
// Sediment
kdsed
kdsed/* * kd_factor*/
} else {
// Bedrock
kd(posi) / (max_slope / mid_slope/*.sqrt()*//*.powf(0.03125)*/)/*.sqrt()*/
kd(posi) as f64 / kd_factor
};
if posj < 0 {
if posj == -1 {
panic!("Disconnected lake!");
}
wh[posi] = 0.0;
// Egress with no outgoing flows.
} else {
let posj = posj as usize;
@ -860,13 +1047,15 @@ fn erode(
let wh_j = wh[posj] as f64;
// If you're on the lake bottom and not right next to your neighbor, don't compute a
// slope.
let mut new_h_i = old_h_i;//old_b_i;
if
/* !is_lake_bottom */ /* !fake_neighbor */
wh_j < old_h_i {
let mut new_h_i = old_h_i;//old_b_i;
// NOTE: Currently assuming that talus angle is the same once the substance is
// submerged in water, but actually that's probably not true.
let h_j = h[posj] as f64/*wh_j*/;
// NOTE: Currently assuming that talus angle is not eroded once the substance is
// totally submerged in water, and that talus angle if part of the substance is
// in water is 0 (or the same as the dry part, if this is set to wh_j), but
// actually that's probably not true.
let h_j = /*h[posj] as f64*/wh_j;
// let h_j = b[posj] as f64;
/* let indirection_idx = indirection[posi];
let is_lake_bottom = indirection_idx < 0;
@ -902,30 +1091,56 @@ fn erode(
// dh = max_slope * NEIGHBOR_DISTANCE * height_scale/* / CONFIG.mountain_scale */ + h_j - old_h_i.
let dh = max_slope * neighbor_distance * height_scale/* / CONFIG.mountain_scale as f64*/;
new_h_i = /*h_j.max*/(h_k + dh).max(new_h_i - l * (mag_slope - max_slope));
let dz = (new_h_i - /*h_j*/h_k).max(0.0) / height_scale/* * CONFIG.mountain_scale as f64*/;
let slope = dz/*.abs()*/ / neighbor_distance;
sums += slope;
/* max_slopes[posi] = /*(mag_slope - max_slope) * */kd(posi);
sums += mag_slope; */
// let slope = dz.signum() * max_slope;
// new_h_i = slope * neighbor_distance * height_scale /* / CONFIG.mountain_scale as f64 */ + h_j;
// sums += max_slope;
if new_h_i <= wh_j {
new_h_i = wh_j;
} else {
let dz = (new_h_i - /*h_j*//*h_k*/wh_j).max(0.0) / height_scale/* * CONFIG.mountain_scale as f64*/;
let slope = dz/*.abs()*/ / neighbor_distance;
sums += slope;
// max_slopes[posi] = /*(mag_slope - max_slope) * */max_slopes[posi].max(kdsed);
/* max_slopes[posi] = /*(mag_slope - max_slope) * */kd(posi);
sums += mag_slope; */
/* if kd_factor < 1.0 {
max_slopes[posi] /= kd_factor;
} else {
max_slopes[posi] *= kd_factor;
} */
// max_slopes[posi] *= kd_factor;
nland += 1;
sumh += new_h_i;
// let slope = dz.signum() * max_slope;
// new_h_i = slope * neighbor_distance * height_scale /* / CONFIG.mountain_scale as f64 */ + h_j;
// sums += max_slope;
}
ntherm += 1;
} else {
/*if kd_factor < 1.0 {
max_slopes[posi] *= kd_factor;
}*/
/* if (old_h_i - old_b_i as f64) <= sediment_thickness {
max_slopes[posi] *= kd_factor;
} */
// max_slopes[posi] *= kd_factor;
sums += mag_slope;
// Just use the computed rate.
nland += 1;
sumh += new_h_i;
}
h/*b*/[posi] = new_h_i as f32;
// h/*b*/[posi] = old_h_i.min(new_h_i) as f32;
// Make sure to update the basement as well!
// b[posi] = old_b_i.min(new_h_i) as f32;
b[posi] = old_b_i.min(old_b_i + (new_h_i - old_h_i)) as f32;
sumh += new_h_i;
// sumh += new_h_i;
}
// Set wh to this node's water height (max of receiver's water height and
// this node's height).
wh[posi] = wh_j.max(new_h_i) as f32;
}
*is_done.at(posi) = done_val;
// *is_done.at(posi) = done_val;
maxh = h[posi].max(maxh);
}
log::debug!(
"Done applying thermal erosion (max height: {:?}) (avg height: {:?}) (avg slope: {:?})",
"Done applying thermal erosion (max height: {:?}) (avg height: {:?}) (avg slope: {:?}) (num land: {:?}) (num thermal: {:?})",
maxh,
if nland == 0 {
f64::INFINITY
@ -937,6 +1152,8 @@ fn erode(
} else {
sums / nland as f64
},
nland,
ntherm,
);
// Apply hillslope diffusion.
@ -949,20 +1166,8 @@ fn erode(
|posi| max_slopes[posi]/*kd*/,
/* kdsed */-1.0,
);
log::debug!(
"Done eroding (max height: {:?}) (avg height: {:?}) (avg slope: {:?})",
maxh,
if nland == 0 {
f64::INFINITY
} else {
sumh / nland as f64
},
if nland == 0 {
f64::INFINITY
} else {
sums / nland as f64
},
);
log::debug!("Done applying diffusion.");
log::debug!("Done eroding.");
}
/// The Planchon-Darboux algorithm for extracting drainage networks.
@ -990,6 +1195,7 @@ pub fn fill_sinks(
.map(|(posi, &h)| {
let is_near_edge = is_ocean(posi);
if is_near_edge {
debug_assert!(h <= 0.0);
h
} else {
infinity
@ -1051,7 +1257,7 @@ pub fn fill_sinks(
/// - A list of chunks on the boundary (non-lake egress points).
/// - The second indirection vector (associating chunk indices with their lake's adjacency list).
/// - The adjacency list (stored in a single vector), indexed by the second indirection vector.
pub fn get_lakes(h: &[f32], downhill: &mut [isize]) -> (usize, Box<[i32]>, Box<[u32]>) {
pub fn get_lakes(h: &[f32], downhill: &mut [isize]) -> (usize, Box<[i32]>, Box<[u32]>, f32) {
// Associates each lake index with its root node (the deepest one in the lake), and a list of
// adjacent lakes. The list of adjacent lakes includes the lake index of the adjacent lake,
// and a node index in the adjacent lake which has a neighbor in this lake. The particular
@ -1124,6 +1330,7 @@ pub fn get_lakes(h: &[f32], downhill: &mut [isize]) -> (usize, Box<[i32]>, Box<[
log::debug!("Old lake roots: {:?}", lake_roots.len());
let newh = newh.into_boxed_slice();
let mut maxh = -f32::INFINITY;
// Now, we know that the sum of all the indirection nodes will be the same as the number of
// nodes. We can allocate a *single* vector with 8 * nodes entries, to be used as storage
// space, and augment our indirection vector with the starting index, resulting in a vector of
@ -1134,6 +1341,8 @@ pub fn get_lakes(h: &[f32], downhill: &mut [isize]) -> (usize, Box<[i32]>, Box<[
let lake_idx_ = indirection_[chunk_idx];
let lake_idx = lake_idx_ as usize;
let height = h[chunk_idx_ as usize];
// While we're here, compute the max elevation difference from zero among all nodes.
maxh = maxh.max(height.abs());
// For every neighbor, check to see whether it is already set; if the neighbor is set,
// its height is ≤ our height. We should search through the edge list for the
// neighbor's lake to see if there's an entry; if not, we insert, and otherwise we
@ -1429,7 +1638,7 @@ pub fn get_lakes(h: &[f32], downhill: &mut [isize]) -> (usize, Box<[i32]>, Box<[
}
});
assert_eq!(newh.len(), downhill.len());
(boundary.len(), indirection, newh.into_boxed_slice())
(boundary.len(), indirection, newh.into_boxed_slice(), maxh)
}
/// Perform erosion n times.
@ -1443,6 +1652,9 @@ pub fn do_erosion(
oldb: impl Fn(usize) -> f32 + Sync,
is_ocean: impl Fn(usize) -> bool + Sync,
uplift: impl Fn(usize) -> f32 + Sync,
kf: impl Fn(usize) -> f32 + Sync,
kd: impl Fn(usize) -> f32 + Sync,
g: impl Fn(usize) -> f32 + Sync,
) -> (Box<[f32]>, Box<[f32]>) {
let oldh_ = (0..WORLD_SIZE.x * WORLD_SIZE.y)
.into_par_iter()
@ -1455,6 +1667,24 @@ pub fn do_erosion(
.map(|posi| oldb(posi))
.collect::<Vec<_>>()
.into_boxed_slice();
// Stream power law erodability constant for fluvial erosion (bedrock)
let kf = (0..WORLD_SIZE.x * WORLD_SIZE.y)
.into_par_iter()
.map(|posi| kf(posi))
.collect::<Vec<_>>()
.into_boxed_slice();
// Stream power law erodability constant for hillslope diffusion (bedrock)
let kd = (0..WORLD_SIZE.x * WORLD_SIZE.y)
.into_par_iter()
.map(|posi| kd(posi))
.collect::<Vec<_>>()
.into_boxed_slice();
// Deposition coefficient
let g = (0..WORLD_SIZE.x * WORLD_SIZE.y)
.into_par_iter()
.map(|posi| g(posi))
.collect::<Vec<_>>()
.into_boxed_slice();
let mut wh = vec![0.0; WORLD_SIZE.x * WORLD_SIZE.y].into_boxed_slice();
// TODO: Don't do this, maybe?
// (To elaborate, maybe we should have varying uplift or compute it some other way).
@ -1475,6 +1705,11 @@ pub fn do_erosion(
.cloned()
.max_by(|a, b| a.partial_cmp(&b).unwrap())
.unwrap();
let max_g = g
.into_par_iter()
.cloned()
.max_by(|a, b| a.partial_cmp(&b).unwrap())
.unwrap();
log::debug!("Max uplift: {:?}", max_uplift);
// Height of terrain, including sediment.
let mut h = oldh_;
@ -1488,12 +1723,15 @@ pub fn do_erosion(
let k_fb = /*(erosion_base as f64 + 2.244 / mmaxh as f64 * /*10.0*//*5.0*//*9.0*//*7.5*//*5.0*//*2.5*//*1.5*/4.0/*1.0*//*3.75*/ * max_uplift as f64) / dt;*/
2.0e-5 * dt;
let kd_bedrock =
/*1e-2*//*0.25e-2*/1e-2 * height_scale * height_scale/* / (CONFIG.mountain_scale as f64 * CONFIG.mountain_scale as f64) */
/*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 */;
let kdsed =
/*1.5e-2*//*1e-4*//*1.25e-2*/1.5e-2 * height_scale * height_scale/* / (CONFIG.mountain_scale as f64 * CONFIG.mountain_scale as f64) */
/*1.5e-2*//*1e-4*//*1.25e-2*/1.5e-2 / 1.0 * height_scale * height_scale/* / (CONFIG.mountain_scale as f64 * CONFIG.mountain_scale as f64) */
/* * k_fb / 2e-5 */;
let kd = |posi: usize| kd_bedrock; // if is_ocean(posi) { /*0.0*/kd_bedrock } else { kd_bedrock };
// let kd = |posi: usize| kd_bedrock; // if is_ocean(posi) { /*0.0*/kd_bedrock } else { kd_bedrock };
let kd = |posi: usize| kd[posi]; // if is_ocean(posi) { /*0.0*/kd_bedrock } else { kd_bedrock };
let kf = |posi: usize| kf[posi];
let g = |posi: usize| g[posi];
// Hillslope diffusion coefficient for sediment.
let mut is_done = bitbox![0; WORLD_SIZE.x * WORLD_SIZE.y];
for i in 0..n {
@ -1509,11 +1747,15 @@ pub fn do_erosion(
i & 1 == 0,
erosion_base,
max_uplift,
kdsed,
max_g,
-1.0,
// kdsed,
seed,
rock_strength_nz,
|posi| uplift[posi],
kf,
kd,
g,
|posi| is_ocean(posi),
);
}

View File

@ -51,8 +51,8 @@ use vek::*;
// but I think that is probably less important since I don't think we actually cast a chunk id to
// float, just coordinates... could be wrong though!
pub const WORLD_SIZE: Vec2<usize> = Vec2 {
x: 1024 * 2,
y: 1024 * 2,
x: 1024,
y: 1024,
};
/// A structure that holds cached noise values and cumulative distribution functions for the input
@ -107,6 +107,19 @@ pub(crate) struct GenCtx {
pub uplift_nz: Worley,
}
pub struct WorldOpts {
/// Set to false to disable seeding elements during worldgen.
pub seed_elements: bool,
}
impl Default for WorldOpts {
fn default() -> Self {
Self {
seed_elements: true,
}
}
}
pub struct WorldSim {
pub seed: u32,
pub(crate) chunks: Vec<SimChunk>,
@ -117,10 +130,10 @@ pub struct WorldSim {
}
impl WorldSim {
pub fn generate(seed: u32) -> Self {
pub fn generate(seed: u32, opts: WorldOpts) -> Self {
let mut rng = ChaChaRng::from_seed(seed_expan::rng_state(seed));
let continent_scale = 5_000.0f64/*32768.0*/.div(32.0).mul(TerrainChunkSize::RECT_SIZE.x as f64);
let rock_lacunarity = 0.5/*HybridMulti::DEFAULT_LACUNARITY*/;
let rock_lacunarity = 0.5/*2.0*//*HybridMulti::DEFAULT_LACUNARITY*/;
let gen_ctx = GenCtx {
turb_x_nz: SuperSimplex::new().set_seed(rng.gen()),
@ -128,6 +141,7 @@ impl WorldSim {
chaos_nz: RidgedMulti::new()
.set_octaves(/*7*//*3*/ /*7*//*3*/7)
.set_frequency(
// RidgedMulti::DEFAULT_FREQUENCY * (5_000.0 / continent_scale)
/*RidgedMulti::DEFAULT_FREQUENCY **/ 3_000.0 * 8.0 / continent_scale,
)
// .set_persistence(RidgedMulti::DEFAULT_LACUNARITY.powf(-(1.0 - 0.5)))
@ -144,10 +158,11 @@ impl WorldSim {
// .set_frequency(1.0 / ((1 << 0) as f64))
// .set_lacunarity(1.0)
// persistence = lacunarity^(-(1.0 - fractal increment))
.set_persistence(HybridMulti_::DEFAULT_LACUNARITY.powf(-(1.0 - /*0.75*/0.75)))
.set_lacunarity(HybridMulti_::DEFAULT_LACUNARITY)
.set_persistence(HybridMulti_::DEFAULT_LACUNARITY.powf(-(1.0 - /*0.75*/0.0)))
// .set_persistence(/*0.5*//*0.5*/0.5 + 1.0 / ((1 << 6) as f64))
// .set_offset(/*0.7*//*0.5*//*0.75*/0.7)
// .set_offset(/*0.7*//*0.5*//*0.75*/0.0)
.set_offset(/*0.7*//*0.5*//*0.75*/0.0)
.set_seed(rng.gen()),
//temp_nz: SuperSimplex::new().set_seed(rng.gen()),
temp_nz: Fbm::new()
@ -192,9 +207,11 @@ impl WorldSim {
town_gen: StructureGen2d::new(rng.gen(), 2048, 1024),
river_seed: RandomField::new(rng.gen()),
rock_strength_nz: /*Fbm*/HybridMulti_/*BasicMulti*//*Fbm*/::new()
.set_octaves(/*6*//*5*//*4*//*5*//*4*/6)
.set_octaves(/*6*//*5*//*4*//*5*//*4*//*6*/10)
.set_lacunarity(rock_lacunarity)
// persistence = lacunarity^(-(1.0 - fractal increment))
// NOTE: In paper, fractal increment is roughly 0.25.
.set_offset(0.0)
.set_persistence(/*0.9*/ /*2.0*//*1.5*//*HybridMulti::DEFAULT_LACUNARITY*/rock_lacunarity.powf(-(1.0 - 0.25/*0.9*/)))
// 256*32/2^4
// (0.5^(-(1.0-0.9)))^4/256/32*2^4*16*32
@ -202,7 +219,7 @@ impl WorldSim {
// (0.5^(-(1.0-0.9)))^1/256/32*2^4*256*4
// (2^(-(1.0-0.9)))^4
// 16.0
.set_frequency(/*0.9*/ /*Fbm*/HybridMulti_::DEFAULT_FREQUENCY / (64.0/*256.0*//*1.0*//*16.0*/ * 32.0/* TerrainChunkSize::RECT_SIZE.x as f64 */))
.set_frequency(/*0.9*/ /*Fbm*//*HybridMulti_::DEFAULT_FREQUENCY*/1.0 / (8.0/*8.0*//*256.0*//*1.0*//*16.0*/ * 32.0/* TerrainChunkSize::RECT_SIZE.x as f64 */))
// .set_persistence(/*0.9*/ /*2.0*/0.67)
// .set_frequency(/*0.9*/ Fbm::DEFAULT_FREQUENCY / (2.0 * 32.0))
// .set_lacunarity(0.5)
@ -210,9 +227,10 @@ impl WorldSim {
uplift_nz: Worley::new()
.set_seed(rng.gen())
.set_frequency(1.0 / (TerrainChunkSize::RECT_SIZE.x as f64 * 256.0))
.set_displacement(0.5)
.set_range_function(RangeFunction::Chebyshev)
.enable_range(true),
// .set_displacement(/*0.5*/0.0)
.set_displacement(/*0.5*/1.0)
.set_range_function(RangeFunction::Euclidean)
// .enable_range(true),
};
let river_seed = &gen_ctx.river_seed;
@ -229,12 +247,30 @@ impl WorldSim {
.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 = /*32.0*//*128.0*/128.0 * height_scale;
let max_erosion_per_delta_t = 128.0/*128.0*//*32.0*/ * height_scale;
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 = /*100*//*50*//*100*/100/*100*//*37*/;//150;//37/*100*/;//50;//50;//37;//50;//37; // /*37*//*29*//*40*//*150*/37; //150;//200;
let n_small_steps = 8;//8;//8; // 8
let n_steps = /*200*/50; // /*100*//*50*//*100*//*100*//*50*//*25*/25/*100*//*37*/;//150;//37/*100*/;//50;//50;//37;//50;//37; // /*37*//*29*//*40*//*150*/37; //150;//200;
let n_small_steps = 8;//50;//8;//8;//8;//8;//8; // 8
// 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 exp_inverse_cdf = |x: f64/*, pow: f64*/| -(-x).ln_1p()/* / ln(pow)*/;
// 2 / pi * ln(tan(pi/2 * p))
let hypsec_inverse_cdf = |x: f64| f64::consts::FRAC_2_PI * ((x * f64::consts::FRAC_PI_2).tan().ln());
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);
// fractal dimension should be between 0 and 0.9999...
// (lacunarity^octaves)^(-H) = persistence^(octaves)
@ -293,9 +329,9 @@ impl WorldSim {
/* .mul(0.25)
.add(0.125) */)
// .add(0.5)
// .sub(0.05)
.sub(0.05)
// .add(0.05)
.add(0.075)
// .add(0.075)
.mul(0.35), /*-0.0175*/
)
})
@ -458,15 +494,70 @@ impl WorldSim {
let is_ocean = get_oceans(old_height);
let is_ocean_fn = |posi: usize| is_ocean[posi];
let uplift_nz_dist = gen_ctx.uplift_nz
.clone()
.enable_range(true);
// Recalculate altitudes without oceans.
// NaNs in these uniform vectors wherever pure_water() returns true.
let (alt_old_no_ocean, alt_old_inverse) = uniform_noise(|posi, _| {
if is_ocean_fn(posi) {
None
} else {
Some(old_height(posi) /*.abs()*/)
}
});
let ((alt_old_no_ocean, alt_old_inverse), (uplift_uniform, _)) = rayon::join(
|| {
uniform_noise(|posi, _| {
if is_ocean_fn(posi) {
None
} else {
Some(old_height(posi) /*.abs()*/)
}
})
},
|| {
uniform_noise(|posi, wposf| {
if is_ocean_fn(posi) {
None
} else {
let turb_wposf =
wposf.div(TerrainChunkSize::RECT_SIZE.map(|e| e as f64)).div(64.0);
let turb = Vec2::new(
gen_ctx.turb_x_nz.get(turb_wposf.into_array()),
gen_ctx.turb_y_nz.get(turb_wposf.into_array()),
) * /*64.0*/32.0 * 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 udist = uplift_nz_dist.get(turb_wposf.into_array())
.min(1.0)
.max(-1.0)
.mul(0.5)
.add(0.5);
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);
let oheight = alt_old[/*(turb_posi / 64) * 64*/posi].0 as f64 - 0.5;
assert!(udist >= 0.0);
assert!(udist <= 1.0);
let uheight_1 = uheight;//.powf(2.0);
let udist_1 = (0.5 - udist).mul(2.0).max(0.0);
let udist_2 = udist.mul(2.0).min(1.0);
let height =
// uheight_1;
// uheight_1 * (/*udist_2*/udist.powf(2.0) * (f64::consts::PI * 2.0 * (1.0 / (1.0 - udist).max(f64::EPSILON)).min(2.5)/*udist * 5.0*/ * 2.0).cos().mul(0.5).add(0.5));
// uheight * udist_ * (udist_ * 4.0 * 2 * f64::consts::PI).sin()
uheight /* * 0.8*/ * udist_1.powf(2.0) +
/*exp_inverse_cdf*/(oheight.max(0.0).min(max_epsilon).abs())/*.powf(2.0)*/ * 0.2 * udist_2.powf(2.0);
// * (1.0 - udist);// uheight * (1.0 - udist)/*oheight*//* * udist*/ + oheight * udist;/*uheight * (1.0 - udist);*/
// let height = uheight * (0.5 - udist) * 0.8 + (oheight.signum() * oheight.max(0.0).abs().powf(2.0)) * 0.2;// * (1.0 - udist);// uheight * (1.0 - udist)/*oheight*//* * udist*/ + oheight * udist;/*uheight * (1.0 - udist);*/
Some(height)
}
})
},
);
let old_height_uniform = |posi: usize| alt_old_no_ocean[posi].0;
let alt_old_min_uniform = 0.0;
@ -494,17 +585,6 @@ impl WorldSim {
// Perform some erosion.
// 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 exp_inverse_cdf = |x: f64/*, pow: f64*/| -(-x).ln_1p()/* / ln(pow)*/;
// 2 / pi * ln(tan(pi/2 * p))
let hypsec_inverse_cdf = |x: f64| f64::consts::FRAC_2_PI * ((x * f64::consts::FRAC_PI_2).tan().ln());
// 2^((2^10-2)/256) = 15.91...
// -ln(1-(1-(2^(-22)*0.5)))
// -ln(1-(1-(2^(-53)*0.5)))
@ -518,12 +598,8 @@ impl WorldSim {
// ((-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);
// ((ln(0.6)-ln(1-0.6)) - (ln(1/(2048*2048))-ln((1-1/(2048*2048)))))/((ln(1-1/(2048*2048))-ln(1-(1-1/(2048*2048)))) - (ln(1/(2048*2048))-ln((1-1/(2048*2048)))))
let inv_func = /*|x: f64| x*//*exp_inverse_cdf*/logit/*hypsec_inverse_cdf*/;
let inv_func = |x: f64| x/*exp_inverse_cdf*//*logit*//*hypsec_inverse_cdf*/;
let alt_exp_min_uniform = /*exp_inverse_cdf*//*logit*/inv_func(min_epsilon);
let alt_exp_max_uniform = /*exp_inverse_cdf*//*logit*/inv_func(max_epsilon);
@ -540,6 +616,82 @@ impl WorldSim {
).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*//*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 kf_func = {
|posi| {
if is_ocean_fn(posi) {
return 1.0e-4;
}
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(64.0);
let turb = Vec2::new(
gen_ctx.turb_x_nz.get(turb_wposf.into_array()),
gen_ctx.turb_y_nz.get(turb_wposf.into_array()),
) * /*64.0*/32.0 * 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);
// kf = 1.5e-4: high-high (plateau [fan sediment])
// kf = 1e-4: high (plateau)
// kf = 2e-5: normal (dyke [unexposed])
// kf = 1e-6: normal-low (dyke [exposed])
// kf = 2e-6: low (mountain)
((1.0 - uheight) * (1.5e-4 - 2.0e-6) + 2.0e-6) as f32
}
};
let kd_func = {
|posi| {
if is_ocean_fn(posi) {
return 1.0e-2;
}
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(64.0);
let turb = Vec2::new(
gen_ctx.turb_x_nz.get(turb_wposf.into_array()),
gen_ctx.turb_y_nz.get(turb_wposf.into_array()),
) * /*64.0*/32.0 * 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);
// kd = 1e-1: high (mountain, dyke)
// kd = 1.5e-2: normal-high (plateau [fan sediment])
// kd = 1e-2: normal (plateau)
1.0e-2
// (uheight * (1.0e-1 - 1.0e-2) + 1.0e-2) as f32
}
};
let g_func =
|posi| {
if /*is_ocean_fn(posi)*/map_edge_factor(posi) == 0.0 {
return 0.0;
}
1.0
};
let uplift_fn =
|posi| {
if is_ocean_fn(posi) {
@ -577,7 +729,7 @@ impl WorldSim {
.mul(0.045)*/)
};
let height =
((old_height_uniform(posi) - alt_old_min_uniform) as f64
((/*old_height_uniform*/uplift_uniform[posi].0/*1*/ - alt_old_min_uniform) as f64
/ (alt_old_max_uniform - alt_old_min_uniform) as f64)
/*((old_height(posi) - alt_old_min) as f64
/ (alt_old_max - alt_old_min) as f64)*/
@ -619,8 +771,8 @@ impl WorldSim {
assert!(height <= 1.0);
// assert!(alt_main >= 0.0);
let (bump_factor, bump_max) = if
/*height < f32::EPSILON as f64 * 0.5/*false*/*/
true {
/*height < f32::EPSILON as f64 * 0.5*//*false*/
/*true*/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,
@ -635,20 +787,34 @@ impl WorldSim {
// tan(pi/6)*32 ~ 18
// tan(54/360*2*pi)*32
// let height = 1.0f64;
let height = gen_ctx.uplift_nz.get(wposf.into_array())
.min(0.5)
.max(-0.5)
.add(0.5);
/* let turb_wposf =
wposf.div(TerrainChunkSize::RECT_SIZE.map(|e| e as f64)).div(64.0);
let turb = Vec2::new(
gen_ctx.turb_x_nz.get(turb_wposf.into_array()),
gen_ctx.turb_y_nz.get(turb_wposf.into_array()),
) * 64.0 * TerrainChunkSize::RECT_SIZE.map(|e| e as f64);
let turb_wposf = wposf + turb;
let height = 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); */
// let height = 1.0 / 7.0f64;
// let height = 0.0 / 31.0f64;
let bfrac = /*erosion_factor(0.5);*/0.0;
let height = (height - bfrac).abs().div(1.0 - bfrac);
let height = height
.mul(31.0 / 32.0)
.add(1.0 / 32.0)
/* .mul(15.0 / 16.0)
.add(1.0 / 16.0) */
/* .mul(5.0 / 8.0)
.add(3.0 / 8.0) */
.mul(7.0 / 8.0)
.add(1.0 / 8.0)
/* .mul(7.0 / 8.0)
.add(1.0 / 8.0) */
.mul(max_erosion_per_delta_t)
.sub(/*1.0 / CONFIG.mountain_scale as f64*/ bump_max)
.add(bump_factor);
@ -698,7 +864,10 @@ impl WorldSim {
/* + spring(alt_main.abs().powf(0.5).min(0.75).mul(60.0).sin(), 4.0)
.mul(0.045)*/)
};
old_height_uniform(posi)/*.powf(2.0)*/
/* 0.0 */
old_height_uniform(posi)/*.powf(2.0)*/ - 0.5
// uplift_fn(posi) * (CONFIG.mountain_scale / max_erosion_per_delta_t as f32)
// 0.0
/* // 0.0
// -/*CONFIG.sea_level / CONFIG.mountain_scale*//* 0.75 */1.0
@ -722,6 +891,9 @@ impl WorldSim {
|posi| alt_func(posi),// if is_ocean_fn(posi) { old_height(posi) } else { 0.0 },
is_ocean_fn,
uplift_fn,
|posi| kf_func(posi),
|posi| kd_func(posi),
|posi| g_func(posi),
);
// Quick "small scale" erosion cycle in order to lower extreme angles.
let (alt, basement) = do_erosion(
@ -734,12 +906,15 @@ impl WorldSim {
|posi| basement[posi],
is_ocean_fn,
|posi| uplift_fn(posi) * (1.0 * height_scale / max_erosion_per_delta_t) as f32,
|posi| kf_func(posi),
|posi| kd_func(posi),
|posi| g_func(posi),
);
let is_ocean = get_oceans(|posi| alt[posi]);
let is_ocean_fn = |posi: usize| is_ocean[posi];
let mut dh = downhill(&alt, /*old_height*/ is_ocean_fn);
let (boundary_len, indirection, water_alt_pos) = get_lakes(&/*water_alt*/alt, &mut dh);
let (boundary_len, indirection, water_alt_pos, _) = get_lakes(&/*water_alt*/alt, &mut dh);
let flux_old = get_drainage(&water_alt_pos, &dh, boundary_len);
let water_height_initial = |chunk_idx| {
@ -787,6 +962,11 @@ impl WorldSim {
};
let water_alt = fill_sinks(water_height_initial, is_ocean_fn);
/* let water_alt = (0..WORLD_SIZE.x * WORLD_SIZE.y)
.into_par_iter()
.map(|posi| water_height_initial(posi))
.collect::<Vec<_>>(); */
let rivers = get_rivers(&water_alt_pos, &water_alt, &dh, &indirection, &flux_old);
let water_alt = indirection
@ -939,7 +1119,9 @@ impl WorldSim {
rng,
};
this.seed_elements();
if opts.seed_elements {
this.seed_elements();
}
this
}
@ -1565,7 +1747,7 @@ impl SimChunk {
is_cliffs: cliff > 0.5 && !is_underwater,
near_cliffs: cliff > 0.2,
tree_density,
forest_kind: if temp > 0.0 {
forest_kind: if temp > CONFIG.temperate_temp {
if temp > CONFIG.desert_temp {
if humidity > CONFIG.jungle_hum {
// Forests in desert temperatures with extremely high humidity

View File

@ -281,13 +281,19 @@ pub fn get_oceans(oldh: impl Fn(usize) -> f32 + Sync) -> BitBox {
// the sides are connected to it, and any subsequent ocean tiles must be connected to it.
let mut is_ocean = bitbox![0; WORLD_SIZE.x * WORLD_SIZE.y];
let mut stack = Vec::new();
let mut do_push = |pos| {
let posi = vec2_as_uniform_idx(pos);
if oldh(posi) <= 0.0 {
stack.push(posi);
}
};
for x in 0..WORLD_SIZE.x as i32 {
stack.push(vec2_as_uniform_idx(Vec2::new(x, 0)));
stack.push(vec2_as_uniform_idx(Vec2::new(x, WORLD_SIZE.y as i32 - 1)));
do_push(Vec2::new(x, 0));
do_push(Vec2::new(x, WORLD_SIZE.y as i32 - 1));
}
for y in 1..WORLD_SIZE.y as i32 - 1 {
stack.push(vec2_as_uniform_idx(Vec2::new(0, y)));
stack.push(vec2_as_uniform_idx(Vec2::new(WORLD_SIZE.x as i32 - 1, y)));
do_push(Vec2::new(0, y));
do_push(Vec2::new(WORLD_SIZE.x as i32 - 1, y));
}
while let Some(chunk_idx) = stack.pop() {
// println!("Ocean chunk {:?}: {:?}", uniform_idx_as_vec2(chunk_idx), oldh(chunk_idx));
@ -532,6 +538,7 @@ impl NoiseFn<Point2<f64>> for HybridMulti {
// Offset and bias to scale into [offset - 1.0, 1.0 + offset] range.
let bias = 1.0;
let mut result = (self.sources[0].get(point) + self.offset) * bias * self.persistence;
let mut exp_scale = 1.0;
let mut scale = self.persistence;
let mut weight = result;
@ -547,7 +554,7 @@ impl NoiseFn<Point2<f64>> for HybridMulti {
let mut signal = (self.sources[x].get(point) + self.offset) * bias;
// Scale the amplitude appropriately for this frequency.
let exp_scale = self.persistence.powi(x as i32);
exp_scale *= self.persistence;
signal *= exp_scale;
// Add it in, weighted by previous octave's noise value.
@ -571,6 +578,7 @@ impl NoiseFn<Point3<f64>> for HybridMulti {
// Offset and bias to scale into [offset - 1.0, 1.0 + offset] range.
let bias = 1.0;
let mut result = (self.sources[0].get(point) + self.offset) * bias * self.persistence;
let mut exp_scale = 1.0;
let mut scale = self.persistence;
let mut weight = result;
@ -586,7 +594,7 @@ impl NoiseFn<Point3<f64>> for HybridMulti {
let mut signal = (self.sources[x].get(point) + self.offset) * bias;
// Scale the amplitude appropriately for this frequency.
let exp_scale = self.persistence.powi(x as i32);
exp_scale *= self.persistence;
signal *= exp_scale;
// Add it in, weighted by previous octave's noise value.
@ -610,7 +618,7 @@ impl NoiseFn<Point4<f64>> for HybridMulti {
// Offset and bias to scale into [offset - 1.0, 1.0 + offset] range.
let bias = 1.0;
let mut result = (self.sources[0].get(point) + self.offset) * bias * self.persistence;
let mut exp_scale = self.persistence;
let mut exp_scale = 1.0;
let mut scale = self.persistence;
let mut weight = result;