mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Removing WORLD_SIZE, part 1.
Erased almost every instance of WORLD_SIZE and replaced it with a local power of two, map_size_lg (which respects certain invariants; see common/src/terrain/map.rs for more details about MapSizeLg). This also means we can avoid a dependency on the world crate from client, as desired. Now that the rest of the code is not expecting a fixed WORLD_SIZE, the next step is to arrange for maps to store their world size, and to use that world size as a basis prior to loading the map (as well, probably, as prior to configuring some of the noise functions).
This commit is contained in:
parent
30b1d2c642
commit
13b6d4d534
3
Cargo.lock
generated
3
Cargo.lock
generated
@ -4565,7 +4565,6 @@ dependencies = [
|
|||||||
"uvth 3.1.1",
|
"uvth 3.1.1",
|
||||||
"vek",
|
"vek",
|
||||||
"veloren-common",
|
"veloren-common",
|
||||||
"veloren-world",
|
|
||||||
"veloren_network",
|
"veloren_network",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -4588,6 +4587,7 @@ dependencies = [
|
|||||||
"rand 0.7.3",
|
"rand 0.7.3",
|
||||||
"rayon",
|
"rayon",
|
||||||
"ron",
|
"ron",
|
||||||
|
"roots",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"specs",
|
"specs",
|
||||||
@ -4733,7 +4733,6 @@ dependencies = [
|
|||||||
"rand_chacha 0.2.2",
|
"rand_chacha 0.2.2",
|
||||||
"rayon",
|
"rayon",
|
||||||
"ron",
|
"ron",
|
||||||
"roots",
|
|
||||||
"serde",
|
"serde",
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
|
@ -6,7 +6,6 @@ edition = "2018"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
common = { package = "veloren-common", path = "../common", features = ["no-assets"] }
|
common = { package = "veloren-common", path = "../common", features = ["no-assets"] }
|
||||||
world = { package = "veloren-world", path = "../world" }
|
|
||||||
network = { package = "veloren_network", path = "../network", default-features = false }
|
network = { package = "veloren_network", path = "../network", default-features = false }
|
||||||
|
|
||||||
byteorder = "1.3.2"
|
byteorder = "1.3.2"
|
||||||
|
@ -28,7 +28,7 @@ use common::{
|
|||||||
recipe::RecipeBook,
|
recipe::RecipeBook,
|
||||||
state::State,
|
state::State,
|
||||||
sync::{Uid, UidAllocator, WorldSyncExt},
|
sync::{Uid, UidAllocator, WorldSyncExt},
|
||||||
terrain::{block::Block, TerrainChunk, TerrainChunkSize},
|
terrain::{block::Block, neighbors, TerrainChunk, TerrainChunkSize},
|
||||||
vol::RectVolSize,
|
vol::RectVolSize,
|
||||||
};
|
};
|
||||||
use futures_executor::block_on;
|
use futures_executor::block_on;
|
||||||
@ -50,9 +50,6 @@ use std::{
|
|||||||
use tracing::{debug, error, trace, warn};
|
use tracing::{debug, error, trace, warn};
|
||||||
use uvth::{ThreadPool, ThreadPoolBuilder};
|
use uvth::{ThreadPool, ThreadPoolBuilder};
|
||||||
use vek::*;
|
use vek::*;
|
||||||
// TODO: remove world dependencies. We should see if we
|
|
||||||
// can pull out map drawing into common somehow.
|
|
||||||
use world::sim::{neighbors, Alt};
|
|
||||||
|
|
||||||
// The duration of network inactivity until the player is kicked
|
// The duration of network inactivity until the player is kicked
|
||||||
// @TODO: in the future, this should be configurable on the server
|
// @TODO: in the future, this should be configurable on the server
|
||||||
@ -187,8 +184,18 @@ impl Client {
|
|||||||
let entity = state.ecs_mut().apply_entity_package(entity_package);
|
let entity = state.ecs_mut().apply_entity_package(entity_package);
|
||||||
*state.ecs_mut().write_resource() = time_of_day;
|
*state.ecs_mut().write_resource() = time_of_day;
|
||||||
|
|
||||||
let map_size = world_map.dimensions;
|
let map_size_lg = common::terrain::MapSizeLg::new(
|
||||||
|
world_map.dimensions_lg,
|
||||||
|
)
|
||||||
|
.map_err(|_| {
|
||||||
|
Error::Other(format!(
|
||||||
|
"Server sent bad world map dimensions: {:?}",
|
||||||
|
world_map.dimensions_lg,
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
let map_size = map_size_lg.chunks();
|
||||||
let max_height = world_map.max_height;
|
let max_height = world_map.max_height;
|
||||||
|
let sea_level = world_map.sea_level;
|
||||||
let rgba = world_map.rgba;
|
let rgba = world_map.rgba;
|
||||||
let alt = world_map.alt;
|
let alt = world_map.alt;
|
||||||
let expected_size =
|
let expected_size =
|
||||||
@ -203,10 +210,9 @@ impl Client {
|
|||||||
}
|
}
|
||||||
let [west, east] = world_map.horizons;
|
let [west, east] = world_map.horizons;
|
||||||
let scale_angle =
|
let scale_angle =
|
||||||
|a: u8| (a as Alt / 255.0 * <Alt as FloatConst>::FRAC_PI_2()).tan();
|
|a: u8| (a as f32 / 255.0 * <f32 as FloatConst>::FRAC_PI_2()).tan();
|
||||||
let scale_height = |h: u8| h as Alt / 255.0 * max_height as Alt;
|
let scale_height = |h: u8| h as f32 / 255.0 * max_height;
|
||||||
let scale_height_big =
|
let scale_height_big = |h: u32| (h >> 3) as f32 / 8191.0 * max_height;
|
||||||
|h: u32| (h >> 3) as Alt / 8191.0 * max_height as Alt;
|
|
||||||
|
|
||||||
debug!("Preparing image...");
|
debug!("Preparing image...");
|
||||||
let unzip_horizons = |(angles, heights): &(Vec<_>, Vec<_>)| {
|
let unzip_horizons = |(angles, heights): &(Vec<_>, Vec<_>)| {
|
||||||
@ -223,13 +229,15 @@ impl Client {
|
|||||||
|
|
||||||
// Redraw map (with shadows this time).
|
// Redraw map (with shadows this time).
|
||||||
let mut world_map = vec![0u32; rgba.len()];
|
let mut world_map = vec![0u32; rgba.len()];
|
||||||
let mut map_config = world::sim::MapConfig::default();
|
let mut map_config = common::terrain::map::MapConfig::orthographic(
|
||||||
map_config.lgain = 1.0;
|
map_size_lg,
|
||||||
map_config.gain = max_height;
|
core::ops::RangeInclusive::new(0.0, max_height),
|
||||||
|
);
|
||||||
|
// map_config.gain = max_height;
|
||||||
map_config.horizons = Some(&horizons);
|
map_config.horizons = Some(&horizons);
|
||||||
// map_config.light_direction = Vec3::new(1.0, -1.0, 0.0);
|
// map_config.light_direction = Vec3::new(1.0, -1.0, 0.0);
|
||||||
map_config.focus.z = 0.0;
|
// map_config.focus.z = 0.0;
|
||||||
let rescale_height = |h: Alt| (h / max_height as Alt) as f32;
|
let rescale_height = |h: f32| h / max_height;
|
||||||
let bounds_check = |pos: Vec2<i32>| {
|
let bounds_check = |pos: Vec2<i32>| {
|
||||||
pos.reduce_partial_min() >= 0
|
pos.reduce_partial_min() >= 0
|
||||||
&& pos.x < map_size.x as i32
|
&& pos.x < map_size.x as i32
|
||||||
@ -246,9 +254,7 @@ impl Client {
|
|||||||
let downhill = {
|
let downhill = {
|
||||||
let mut best = -1;
|
let mut best = -1;
|
||||||
let mut besth = alti;
|
let mut besth = alti;
|
||||||
// TODO: Fix to work for dynamic WORLD_SIZE (i.e.
|
for nposi in neighbors(map_size_lg, posi) {
|
||||||
// map_size).
|
|
||||||
for nposi in neighbors(posi) {
|
|
||||||
let nbh = alt[nposi];
|
let nbh = alt[nposi];
|
||||||
if nbh < besth {
|
if nbh < besth {
|
||||||
besth = nbh;
|
besth = nbh;
|
||||||
@ -278,9 +284,9 @@ impl Client {
|
|||||||
wpos + TerrainChunkSize::RECT_SIZE.map(|e| e as i32),
|
wpos + TerrainChunkSize::RECT_SIZE.map(|e| e as i32),
|
||||||
);
|
);
|
||||||
let alt = rescale_height(scale_height_big(alt));
|
let alt = rescale_height(scale_height_big(alt));
|
||||||
world::sim::MapSample {
|
common::terrain::map::MapSample {
|
||||||
rgb: Rgb::from(rgba),
|
rgb: Rgb::from(rgba),
|
||||||
alt: alt as Alt,
|
alt: f64::from(alt),
|
||||||
downhill_wpos,
|
downhill_wpos,
|
||||||
connections: None,
|
connections: None,
|
||||||
}
|
}
|
||||||
@ -326,10 +332,7 @@ impl Client {
|
|||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
let lod_horizon = horizons; //make_raw(&horizons)?;
|
let lod_horizon = horizons; //make_raw(&horizons)?;
|
||||||
// TODO: Get sea_level from server.
|
// TODO: Get sea_level from server.
|
||||||
let map_bounds = Vec2::new(
|
let map_bounds = Vec2::new(sea_level, max_height);
|
||||||
/* map_config.focus.z */ world::CONFIG.sea_level,
|
|
||||||
/* map_config.gain */ max_height,
|
|
||||||
);
|
|
||||||
debug!("Done preparing image...");
|
debug!("Done preparing image...");
|
||||||
|
|
||||||
break Ok((
|
break Ok((
|
||||||
|
@ -11,6 +11,7 @@ no-assets = []
|
|||||||
arraygen = "0.1.13"
|
arraygen = "0.1.13"
|
||||||
specs-idvs = { git = "https://gitlab.com/veloren/specs-idvs.git", branch = "specs-git" }
|
specs-idvs = { git = "https://gitlab.com/veloren/specs-idvs.git", branch = "specs-git" }
|
||||||
|
|
||||||
|
roots = "0.0.5"
|
||||||
specs = { git = "https://github.com/amethyst/specs.git", features = ["serde", "storage-event-control"], rev = "7a2e348ab2223818bad487695c66c43db88050a5" }
|
specs = { git = "https://github.com/amethyst/specs.git", features = ["serde", "storage-event-control"], rev = "7a2e348ab2223818bad487695c66c43db88050a5" }
|
||||||
vek = { version = "0.11.0", features = ["serde"] }
|
vek = { version = "0.11.0", features = ["serde"] }
|
||||||
dot_vox = "4.0"
|
dot_vox = "4.0"
|
||||||
|
@ -3,6 +3,8 @@
|
|||||||
#![type_length_limit = "1664759"]
|
#![type_length_limit = "1664759"]
|
||||||
#![feature(
|
#![feature(
|
||||||
arbitrary_enum_discriminant,
|
arbitrary_enum_discriminant,
|
||||||
|
const_checked_int_methods,
|
||||||
|
const_if_match,
|
||||||
option_unwrap_none,
|
option_unwrap_none,
|
||||||
bool_to_option,
|
bool_to_option,
|
||||||
label_break_value,
|
label_break_value,
|
||||||
|
@ -66,8 +66,12 @@ pub struct CharacterInfo {
|
|||||||
/// repeating the "small angles" optimization that works well on more detailed
|
/// repeating the "small angles" optimization that works well on more detailed
|
||||||
/// shadow maps intended for height maps.
|
/// shadow maps intended for height maps.
|
||||||
pub struct WorldMapMsg {
|
pub struct WorldMapMsg {
|
||||||
/// World map dimensions (width × height)
|
/// Log base 2 of world map dimensions (width × height) in chunks.
|
||||||
pub dimensions: Vec2<u16>,
|
///
|
||||||
|
/// NOTE: Invariant: chunk count fits in a u16.
|
||||||
|
pub dimensions_lg: Vec2<u32>,
|
||||||
|
/// Sea level (used to provide a base altitude).
|
||||||
|
pub sea_level: f32,
|
||||||
/// Max height (used to scale altitudes).
|
/// Max height (used to scale altitudes).
|
||||||
pub max_height: f32,
|
pub max_height: f32,
|
||||||
/// RGB+A; the alpha channel is currently unused, but will be used in the
|
/// RGB+A; the alpha channel is currently unused, but will be used in the
|
||||||
|
719
common/src/terrain/map.rs
Normal file
719
common/src/terrain/map.rs
Normal file
@ -0,0 +1,719 @@
|
|||||||
|
use super::{
|
||||||
|
neighbors, quadratic_nearest_point, river_spline_coeffs, uniform_idx_as_vec2,
|
||||||
|
vec2_as_uniform_idx, TerrainChunkSize, NEIGHBOR_DELTA, TERRAIN_CHUNK_BLOCKS_LG,
|
||||||
|
};
|
||||||
|
use crate::vol::RectVolSize;
|
||||||
|
use core::{f32, f64, iter, ops::RangeInclusive};
|
||||||
|
use vek::*;
|
||||||
|
|
||||||
|
/// Base two logarithm of the maximum size of the precomputed world, in meters,
|
||||||
|
/// along the x (E/W) and y (N/S) dimensions.
|
||||||
|
///
|
||||||
|
/// NOTE: Each dimension is guaranteed to be a power of 2, so the logarithm is
|
||||||
|
/// exact. This is so that it is possible (at least in theory) for compiler or
|
||||||
|
/// runtime optimizations exploiting this are possible. For example, division
|
||||||
|
/// by the chunk size can turn into a bit shift.
|
||||||
|
///
|
||||||
|
/// NOTE: As an invariant, this value is at least [TERRAIN_CHUNK_BLOCKS_LG].
|
||||||
|
///
|
||||||
|
/// NOTE: As an invariant, `(1 << [MAX_WORLD_BLOCKS_LG])` fits in an i32.
|
||||||
|
///
|
||||||
|
/// TODO: Add static assertions for the above invariants.
|
||||||
|
///
|
||||||
|
/// Currently, we define the maximum to be 19 (corresponding to 2^19 m) for both
|
||||||
|
/// directions. This value was derived by backwards reasoning from the following
|
||||||
|
/// conservative estimate of the maximum landmass area (using an approximation
|
||||||
|
/// of 1024 blocks / km instead of 1000 blocks / km, which will result in an
|
||||||
|
/// estimate that is strictly lower than the real landmass):
|
||||||
|
///
|
||||||
|
/// Max area (km²)
|
||||||
|
/// ≌ (2^19 blocks * 1 km / 1024 blocks)^2
|
||||||
|
/// = 2^((19 - 10) * 2) km²
|
||||||
|
/// = 2^18 km²
|
||||||
|
/// = 262,144 km²
|
||||||
|
///
|
||||||
|
/// which is roughly the same area as the entire United Kingdom, and twice the
|
||||||
|
/// horizontal extent of Dwarf Fortress's largest map. Besides the comparison
|
||||||
|
/// to other games without infinite or near-infinite maps (like Dwarf Fortress),
|
||||||
|
/// there are other reasons to choose this as a good maximum size:
|
||||||
|
///
|
||||||
|
/// * It is large enough to include geological features of fairly realistic
|
||||||
|
/// scale. It may be hard to do justice to truly enormous features like the
|
||||||
|
/// Amazon River, and natural temperature variation not related to altitude
|
||||||
|
/// would probably not produce climate extremes on an Earth-like planet, but
|
||||||
|
/// it can comfortably fit enormous river basins, Everest-scale mountains,
|
||||||
|
/// large islands and inland lakes, vast forests and deserts, and so on.
|
||||||
|
///
|
||||||
|
/// * It is large enough that making it from one side of the map to another will
|
||||||
|
/// take a *very* long time. We show this with two examples. In each
|
||||||
|
/// example, travel is either purely horizontal or purely vertical (to
|
||||||
|
/// minimize distance traveled) across the whole map, and we assume there are
|
||||||
|
/// no obstacles or slopes.
|
||||||
|
///
|
||||||
|
/// In example 1, a human is walking at the (real-time) speed of the fastest
|
||||||
|
/// marathon runners (around 6 blocks / real-time s). We assume the human can
|
||||||
|
/// maintain this pace indefinitely without stopping. Then crossing the map
|
||||||
|
/// will take about:
|
||||||
|
///
|
||||||
|
/// 2^19 blocks * 1 real-time s / 6 blocks * 1 real-time min / 60 real-time s
|
||||||
|
/// * 1 real-time hr / 60 real-time min * 1 real-time days / 24 hr = 2^19 / 6 /
|
||||||
|
/// 60 / 60 / 24 real-time days ≌ 1 real-time day.
|
||||||
|
///
|
||||||
|
/// That's right--it will take a full day of *real* time to cross the map at
|
||||||
|
/// an apparent speed of 6 m / s. Moreover, since in-game time passes at a
|
||||||
|
/// rate of 1 in-game min / 1 in-game s, this would also take *60 days* of
|
||||||
|
/// in-game time.
|
||||||
|
///
|
||||||
|
/// Still though, this is the rate of an ordinary human. And besides that, if
|
||||||
|
/// we instead had a marathon runner traveling at 6 m / in-game s, it would
|
||||||
|
/// take just 1 day of in-game time for the runner to cross the map, or a mere
|
||||||
|
/// 0.4 hr of real time. To show that this rate of travel is unrealistic (and
|
||||||
|
/// perhaps make an eventual argument for a slower real-time to in-game time
|
||||||
|
/// conversion rate), our second example will consist of a high-speed train
|
||||||
|
/// running at 300 km / real-time h (the fastest real-world high speed train
|
||||||
|
/// averages under 270 k m / h, with 300 km / h as the designed top speed).
|
||||||
|
/// For a train traveling at this apparent speed (in real time), crossing the
|
||||||
|
/// map would take:
|
||||||
|
///
|
||||||
|
/// 2^19 blocks * 1 km / 1000 blocks * 1 real-time hr / 300 km
|
||||||
|
/// = 2^19 / 1000 / 300 real-time hr
|
||||||
|
/// ≌ 1.75 real-time hr
|
||||||
|
///
|
||||||
|
/// = 2^19 / 1000 / 300 real-time hr * 60 in-game hr / real-time hr
|
||||||
|
/// * 1 in-game days / 24 in-game hr
|
||||||
|
/// = 2^19 / 1000 / 300 * 60 / 24 in-game days
|
||||||
|
/// ≌ 4.37 in-game days
|
||||||
|
///
|
||||||
|
/// In other words, something faster in real-time than any existing high-speed
|
||||||
|
/// train would be over 4 times slower (in real-time) than our hypothetical
|
||||||
|
/// top marathon runner running at 6 m / s in in-game speed. This suggests
|
||||||
|
/// that the gap between in-game time and real-time is probably much too large
|
||||||
|
/// for most purposes; however, what it definitely shows is that even
|
||||||
|
/// extremely fast in-game transport across the world will not trivialize its
|
||||||
|
/// size.
|
||||||
|
///
|
||||||
|
/// It follows that cities or towns of realistic scale, player housing,
|
||||||
|
/// fields, and so on, will all fit comfortably on a map of this size, while
|
||||||
|
/// at the same time still being reachable by non-warping, in-game mechanisms
|
||||||
|
/// (such as high-speed transit). It also provides plenty of room for mounts
|
||||||
|
/// of varying speeds, which can help ensure that players don't feel cramped or
|
||||||
|
/// deliberately slowed down by their own speed.
|
||||||
|
///
|
||||||
|
/// * It is small enough that it is (barely) plausible that we could still
|
||||||
|
/// generate maps for a world of this size using detailed and realistic
|
||||||
|
/// erosion algorithms. At 1/4 of this map size along each dimension,
|
||||||
|
/// generation currently takes around 5 hours on a good computer, and one
|
||||||
|
/// could imagine (since the bottleneck step appears to be roughly O(n)) that
|
||||||
|
/// with a smart implementation generation times of under a week could be
|
||||||
|
/// achievable.
|
||||||
|
///
|
||||||
|
/// * The map extends further than the resolution of human eyesight under
|
||||||
|
/// Earthlike conditions, even from tall mountains across clear landscapes.
|
||||||
|
/// According to one calculation, even from Mt. Everest in the absence of
|
||||||
|
/// cloud cover, you could only see for about 339 km before the Earth's
|
||||||
|
/// horizon prevented you from seeing further, and other sources suggest that
|
||||||
|
/// in practice the limit is closer to 160 km under realistic conditions. This
|
||||||
|
/// implies that making the map much larger in a realistic way would require
|
||||||
|
/// incorporating curvature, and also implies that any features that cannot
|
||||||
|
/// fit on the map would not (under realistic atmospheric conditions) be fully
|
||||||
|
/// visible from any point on Earth. Therefore, even if we cannot represent
|
||||||
|
/// features larger than this accurately, nothing should be amiss from a
|
||||||
|
/// visual perspective, so this should not significantly impact the player
|
||||||
|
/// experience.
|
||||||
|
pub const MAX_WORLD_BLOCKS_LG: Vec2<u32> = Vec2 { x: 19, y: 19 };
|
||||||
|
|
||||||
|
/// Base two logarithm of a world size, in chunks, per dimension
|
||||||
|
/// (each dimension must be a power of 2, so the logarithm is exact).
|
||||||
|
///
|
||||||
|
/// NOTE: As an invariant, each dimension must be between 0 and
|
||||||
|
/// `[MAX_WORLD_BLOCKS_LG] - [TERRAIN_CHUNK_BLOCKS_LG]`.
|
||||||
|
///
|
||||||
|
/// NOTE: As an invariant, `(1 << ([DEFAULT_WORLD_CHUNKS_LG] +
|
||||||
|
/// [TERRAIN_CHUNK_BLOCKS_LG]))` fits in an i32 (derived from the invariant
|
||||||
|
/// on [MAX_WORLD_BLOCKS_LG]).
|
||||||
|
///
|
||||||
|
/// NOTE: As an invariant, each dimension (in chunks) must fit in a u16.
|
||||||
|
///
|
||||||
|
/// NOTE: As an invariant, the product of dimensions (in chunks) must fit in a
|
||||||
|
/// usize.
|
||||||
|
///
|
||||||
|
/// These invariants are all checked on construction of a `MapSizeLg`.
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct MapSizeLg(Vec2<u32>);
|
||||||
|
|
||||||
|
impl MapSizeLg {
|
||||||
|
/// Construct a new `MapSizeLg`, returning an error if the needed invariants
|
||||||
|
/// do not hold and the vector otherwise.
|
||||||
|
///
|
||||||
|
/// TODO: In the future, we may use unsafe code to assert to the compiler
|
||||||
|
/// that these invariants indeed hold, safely opening up optimizations
|
||||||
|
/// that might not otherwise be available at runtime.
|
||||||
|
#[inline(always)]
|
||||||
|
pub const fn new(map_size_lg: Vec2<u32>) -> Result<Self, ()> {
|
||||||
|
// Assertion on dimensions: must be between
|
||||||
|
// 0 and MAX_WORLD_BLOCKS_LG] - [TERRAIN_CHUNK_BLOCKS_LG
|
||||||
|
let is_le_max = map_size_lg.x <= MAX_WORLD_BLOCKS_LG.x - TERRAIN_CHUNK_BLOCKS_LG
|
||||||
|
&& map_size_lg.y <= MAX_WORLD_BLOCKS_LG.y - TERRAIN_CHUNK_BLOCKS_LG;
|
||||||
|
// Assertion on dimensions: chunks must fit in a u16.
|
||||||
|
let chunks_in_range =
|
||||||
|
/* 1u16.checked_shl(map_size_lg.x).is_some() &&
|
||||||
|
1u16.checked_shl(map_size_lg.y).is_some(); */
|
||||||
|
map_size_lg.x <= 16 &&
|
||||||
|
map_size_lg.y <= 16;
|
||||||
|
if is_le_max && chunks_in_range {
|
||||||
|
// Assertion on dimensions: blocks must fit in a i32.
|
||||||
|
let blocks_in_range =
|
||||||
|
/* 1i32.checked_shl(map_size_lg.x + TERRAIN_CHUNK_BLOCKS_LG).is_some() &&
|
||||||
|
1i32.checked_shl(map_size_lg.y + TERRAIN_CHUNK_BLOCKS_LG).is_some(); */
|
||||||
|
map_size_lg.x + TERRAIN_CHUNK_BLOCKS_LG < 32 &&
|
||||||
|
map_size_lg.y + TERRAIN_CHUNK_BLOCKS_LG < 32;
|
||||||
|
// Assertion on dimensions: product of dimensions must fit in a usize.
|
||||||
|
let chunks_product_in_range =
|
||||||
|
if let Some(_) = 1usize.checked_shl(map_size_lg.x + map_size_lg.y) {
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
if blocks_in_range && chunks_product_in_range {
|
||||||
|
// Cleared all invariants.
|
||||||
|
Ok(MapSizeLg(map_size_lg))
|
||||||
|
} else {
|
||||||
|
Err(())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
/// Acquire the `MapSizeLg`'s inner vector.
|
||||||
|
pub const fn vec(self) -> Vec2<u32> { self.0 }
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
/// Get the size of this map in chunks.
|
||||||
|
pub const fn chunks(self) -> Vec2<u16> { Vec2::new(1 << self.0.x, 1 << self.0.y) }
|
||||||
|
|
||||||
|
/// Get the size of an array of the correct size to hold all chunks.
|
||||||
|
pub const fn chunks_len(self) -> usize { 1 << (self.0.x + self.0.y) }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<MapSizeLg> for Vec2<u32> {
|
||||||
|
#[inline(always)]
|
||||||
|
fn from(size: MapSizeLg) -> Self { size.vec() }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct MapConfig<'a> {
|
||||||
|
/// Base two logarithm of the chunk dimensions of the base map.
|
||||||
|
/// Has no default; set explicitly during initial orthographic projection.
|
||||||
|
pub map_size_lg: MapSizeLg,
|
||||||
|
/// Dimensions of the window being written to.
|
||||||
|
///
|
||||||
|
/// Defaults to `1 << [MapConfig::map_size_lg]`.
|
||||||
|
pub dimensions: Vec2<usize>,
|
||||||
|
/// x, y, and z of top left of map.
|
||||||
|
///
|
||||||
|
/// Default x and y are 0.0; no reasonable default for z, so set during
|
||||||
|
/// initial orthographic projection.
|
||||||
|
pub focus: Vec3<f64>,
|
||||||
|
/// Altitude is divided by gain and clamped to [0, 1]; thus, decreasing gain
|
||||||
|
/// makes smaller differences in altitude appear larger.
|
||||||
|
///
|
||||||
|
/// No reasonable default for z; set during initial orthographic projection.
|
||||||
|
pub gain: f32,
|
||||||
|
/// `fov` is used for shading purposes and refers to how much impact a
|
||||||
|
/// change in the z direction has on the perceived slope relative to the
|
||||||
|
/// same change in x and y.
|
||||||
|
///
|
||||||
|
/// It is stored as cos θ in the range (0, 1\] where θ is the FOV
|
||||||
|
/// "half-angle" used for perspective projection. At 1.0, we treat it
|
||||||
|
/// as the limit value for θ = 90 degrees, and use an orthographic
|
||||||
|
/// projection.
|
||||||
|
///
|
||||||
|
/// Defaults to 1.0.
|
||||||
|
///
|
||||||
|
/// FIXME: This is a hack that tries to incorrectly implement a variant of
|
||||||
|
/// perspective projection (which generates ∂P/∂x and ∂P/∂y for screen
|
||||||
|
/// coordinate P by using the hyperbolic function \[assuming frustum of
|
||||||
|
/// \[l, r, b, t, n, f\], rh coordinates, and output from -1 to 1 in
|
||||||
|
/// s/t, 0 to 1 in r, and NDC is left-handed \[so visible z ranges from
|
||||||
|
/// -n to -f\]\]):
|
||||||
|
///
|
||||||
|
/// P.s(x, y, z) = -1 + 2(-n/z x - l) / ( r - l)
|
||||||
|
/// P.t(x, y, z) = -1 + 2(-n/z y - b) / ( t - b)
|
||||||
|
/// P.r(x, y, z) = 0 + -f(-n/z - 1) / ( f - n)
|
||||||
|
///
|
||||||
|
/// Then arbitrarily using W_e_x = (r - l) as the width of the projected
|
||||||
|
/// image, we have W_e_x = 2 n_e tan θ ⇒ tan Θ = (r - l) / (2n_e), for a
|
||||||
|
/// perspective projection
|
||||||
|
///
|
||||||
|
/// (where θ is the half-angle of the FOV).
|
||||||
|
///
|
||||||
|
/// Taking the limit as θ → 90, we show that this degenrates to an
|
||||||
|
/// orthogonal projection:
|
||||||
|
///
|
||||||
|
/// lim{n → ∞}(-f(-n / z - 1) / (f - n)) = -(z - -n) / (f - n).
|
||||||
|
///
|
||||||
|
/// (Proof not currently included, but has been formalized for the P.r case
|
||||||
|
/// in Coq-tactic notation; the proof can be added on request, but is
|
||||||
|
/// large and probably not well-suited to Rust documentation).
|
||||||
|
///
|
||||||
|
/// For this reason, we feel free to store `fov` as cos θ in the range (0,
|
||||||
|
/// 1\].
|
||||||
|
///
|
||||||
|
/// However, `fov` does not actually work properly yet, so for now we just
|
||||||
|
/// treat it as a visual gimmick.
|
||||||
|
pub fov: f64,
|
||||||
|
/// Scale is like gain, but for x and y rather than z.
|
||||||
|
///
|
||||||
|
/// Defaults to (1 << world_size_lg).x / dimensions.x (NOTE: fractional, not
|
||||||
|
/// integer, division!).
|
||||||
|
pub scale: f64,
|
||||||
|
/// Vector that indicates which direction light is coming from, if shading
|
||||||
|
/// is turned on.
|
||||||
|
///
|
||||||
|
/// 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"
|
||||||
|
///
|
||||||
|
/// Defaults to (-0.8, -1.0, 0.3).
|
||||||
|
pub light_direction: Vec3<f64>,
|
||||||
|
/// If Some, uses the provided horizon map.
|
||||||
|
///
|
||||||
|
/// Defaults to None.
|
||||||
|
pub horizons: Option<&'a [(Vec<f32>, Vec<f32>); 2]>,
|
||||||
|
/// If true, only the basement (bedrock) is used for altitude; otherwise,
|
||||||
|
/// the surface is used.
|
||||||
|
///
|
||||||
|
/// Defaults to false.
|
||||||
|
pub is_basement: bool,
|
||||||
|
/// If true, water is rendered; otherwise, the surface without water is
|
||||||
|
/// rendered, even if it is underwater.
|
||||||
|
///
|
||||||
|
/// Defaults to true.
|
||||||
|
pub is_water: bool,
|
||||||
|
/// If true, 3D lighting and shading are turned on. Otherwise, a plain
|
||||||
|
/// altitude map is used.
|
||||||
|
///
|
||||||
|
/// Defaults to true.
|
||||||
|
pub is_shaded: bool,
|
||||||
|
/// If true, the red component of the image is also used for temperature
|
||||||
|
/// (redder is hotter). Defaults to false.
|
||||||
|
pub is_temperature: bool,
|
||||||
|
/// If true, the blue component of the image is also used for humidity
|
||||||
|
/// (bluer is wetter).
|
||||||
|
///
|
||||||
|
/// Defaults to false.
|
||||||
|
pub is_humidity: bool,
|
||||||
|
/// Record debug information.
|
||||||
|
///
|
||||||
|
/// Defaults to false.
|
||||||
|
pub is_debug: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const QUADRANTS: usize = 4;
|
||||||
|
|
||||||
|
pub struct MapDebug {
|
||||||
|
pub quads: [[u32; QUADRANTS]; QUADRANTS],
|
||||||
|
pub rivers: u32,
|
||||||
|
pub lakes: u32,
|
||||||
|
pub oceans: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Connection kind (per edge). Currently just supports rivers, but may be
|
||||||
|
/// extended to support paths or at least one other kind of connection.
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub enum ConnectionKind {
|
||||||
|
/// Connection forms a visible river.
|
||||||
|
River,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Map connection (per edge).
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct Connection {
|
||||||
|
/// The kind of connection this is (e.g. river or path).
|
||||||
|
pub kind: ConnectionKind,
|
||||||
|
/// Assumed to be the "b" part of a 2d quadratic function.
|
||||||
|
pub spline_derivative: Vec2<f32>,
|
||||||
|
/// Width of the connection.
|
||||||
|
pub width: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Per-chunk data the map needs to be able to sample in order to correctly
|
||||||
|
/// render.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct MapSample {
|
||||||
|
/// the base RGB color for a particular map pixel using the current settings
|
||||||
|
/// (i.e. the color *without* lighting).
|
||||||
|
pub rgb: Rgb<u8>,
|
||||||
|
/// Surface altitude information
|
||||||
|
/// (correctly reflecting settings like is_basement and is_water)
|
||||||
|
pub alt: f64,
|
||||||
|
/// Downhill chunk (may not be meaningful on ocean tiles, or at least edge
|
||||||
|
/// tiles)
|
||||||
|
pub downhill_wpos: Vec2<i32>,
|
||||||
|
/// Connection information about any connections to/from this chunk (e.g.
|
||||||
|
/// rivers).
|
||||||
|
///
|
||||||
|
/// Connections at each index correspond to the same index in
|
||||||
|
/// NEIGHBOR_DELTA.
|
||||||
|
pub connections: Option<[Option<Connection>; 8]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> MapConfig<'a> {
|
||||||
|
/// Constructs the configuration settings for an orthographic projection of
|
||||||
|
/// a map from the top down, rendering (by default) the complete map to
|
||||||
|
/// an image such that the chunk:pixel ratio is 1:1.
|
||||||
|
///
|
||||||
|
/// Takes two arguments: the base two logarithm of the horizontal map extent
|
||||||
|
/// (in chunks), and the z bounds of the projection.
|
||||||
|
pub fn orthographic(map_size_lg: MapSizeLg, z_bounds: RangeInclusive<f32>) -> Self {
|
||||||
|
assert!(z_bounds.start() <= z_bounds.end());
|
||||||
|
// NOTE: Safe cast since map_size_lg is restricted by the prior assert.
|
||||||
|
let dimensions = map_size_lg.chunks().map(usize::from);
|
||||||
|
Self {
|
||||||
|
map_size_lg,
|
||||||
|
dimensions,
|
||||||
|
focus: Vec3::new(0.0, 0.0, f64::from(*z_bounds.start())),
|
||||||
|
gain: z_bounds.end() - z_bounds.start(),
|
||||||
|
fov: 1.0,
|
||||||
|
scale: 1.0,
|
||||||
|
light_direction: Vec3::new(-1.2, -1.0, 0.8),
|
||||||
|
horizons: None,
|
||||||
|
|
||||||
|
is_basement: false,
|
||||||
|
is_water: true,
|
||||||
|
is_shaded: true,
|
||||||
|
is_temperature: false,
|
||||||
|
is_humidity: false,
|
||||||
|
is_debug: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the base 2 logarithm of the underlying map size.
|
||||||
|
pub fn map_size_lg(&self) -> MapSizeLg { self.map_size_lg }
|
||||||
|
|
||||||
|
/// Generates a map image using the specified settings. Note that it will
|
||||||
|
/// write from left to write from (0, 0) to dimensions - 1, inclusive,
|
||||||
|
/// with 4 1-byte color components provided as (r, g, b, a). It is up
|
||||||
|
/// to the caller to provide a function that translates this information
|
||||||
|
/// into the correct format for a buffer and writes to it.
|
||||||
|
///
|
||||||
|
/// sample_pos is a function that, given a chunk position, returns enough
|
||||||
|
/// information about the chunk to attempt to render it on the map.
|
||||||
|
/// When in doubt, try using `MapConfig::sample_pos` for this.
|
||||||
|
///
|
||||||
|
/// sample_wpos is a simple function that, given a *column* position,
|
||||||
|
/// returns the approximate altitude at that column. When in doubt, try
|
||||||
|
/// using `MapConfig::sample_wpos` for this.
|
||||||
|
#[allow(clippy::if_same_then_else)] // TODO: Pending review in #587
|
||||||
|
#[allow(clippy::unnested_or_patterns)] // TODO: Pending review in #587
|
||||||
|
#[allow(clippy::many_single_char_names)]
|
||||||
|
pub fn generate(
|
||||||
|
&self,
|
||||||
|
sample_pos: impl Fn(Vec2<i32>) -> MapSample,
|
||||||
|
sample_wpos: impl Fn(Vec2<i32>) -> f32,
|
||||||
|
mut write_pixel: impl FnMut(Vec2<usize>, (u8, u8, u8, u8)),
|
||||||
|
) -> MapDebug {
|
||||||
|
let MapConfig {
|
||||||
|
map_size_lg,
|
||||||
|
dimensions,
|
||||||
|
focus,
|
||||||
|
gain,
|
||||||
|
fov,
|
||||||
|
scale,
|
||||||
|
light_direction,
|
||||||
|
horizons,
|
||||||
|
|
||||||
|
is_shaded,
|
||||||
|
// is_debug,
|
||||||
|
..
|
||||||
|
} = *self;
|
||||||
|
|
||||||
|
let light_direction = Vec3::new(
|
||||||
|
light_direction.x,
|
||||||
|
light_direction.y,
|
||||||
|
0.0, // we currently ignore light_direction.z.
|
||||||
|
);
|
||||||
|
let light_shadow_dir = if light_direction.x >= 0.0 { 0 } else { 1 };
|
||||||
|
let horizon_map = horizons.map(|horizons| &horizons[light_shadow_dir]);
|
||||||
|
let light = light_direction.normalized();
|
||||||
|
let /*mut */quads = [[0u32; QUADRANTS]; QUADRANTS];
|
||||||
|
let /*mut */rivers = 0u32;
|
||||||
|
let /*mut */lakes = 0u32;
|
||||||
|
let /*mut */oceans = 0u32;
|
||||||
|
|
||||||
|
let focus_rect = Vec2::from(focus);
|
||||||
|
|
||||||
|
let chunk_size = TerrainChunkSize::RECT_SIZE.map(|e| e as f64);
|
||||||
|
|
||||||
|
/* // NOTE: Asserting this to enable LLVM optimizations. Ideally we should come up
|
||||||
|
// with a principled way to do this (especially one with no runtime
|
||||||
|
// cost).
|
||||||
|
assert!(
|
||||||
|
map_size_lg
|
||||||
|
.vec()
|
||||||
|
.cmple(&(MAX_WORLD_BLOCKS_LG - TERRAIN_CHUNK_BLOCKS_LG))
|
||||||
|
.reduce_and()
|
||||||
|
); */
|
||||||
|
let world_size = map_size_lg.chunks();
|
||||||
|
|
||||||
|
(0..dimensions.y * dimensions.x).for_each(|chunk_idx| {
|
||||||
|
let i = chunk_idx % dimensions.x as usize;
|
||||||
|
let j = chunk_idx / dimensions.x as usize;
|
||||||
|
|
||||||
|
let wposf = focus_rect + Vec2::new(i as f64, j as f64) * scale;
|
||||||
|
let pos = wposf.map(|e: f64| e as i32);
|
||||||
|
let wposf = wposf * chunk_size;
|
||||||
|
|
||||||
|
let chunk_idx = if pos.reduce_partial_min() >= 0
|
||||||
|
&& pos.x < world_size.x as i32
|
||||||
|
&& pos.y < world_size.y as i32
|
||||||
|
{
|
||||||
|
Some(vec2_as_uniform_idx(map_size_lg, pos))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let MapSample {
|
||||||
|
rgb,
|
||||||
|
alt,
|
||||||
|
downhill_wpos,
|
||||||
|
..
|
||||||
|
} = sample_pos(pos);
|
||||||
|
|
||||||
|
let alt = alt as f32;
|
||||||
|
let wposi = pos * TerrainChunkSize::RECT_SIZE.map(|e| e as i32);
|
||||||
|
let mut rgb = rgb.map(|e| e as f64 / 255.0);
|
||||||
|
|
||||||
|
// Material properties:
|
||||||
|
//
|
||||||
|
// For each material in the scene,
|
||||||
|
// k_s = (RGB) specular reflection constant
|
||||||
|
let mut k_s = Rgb::new(1.0, 1.0, 1.0);
|
||||||
|
// k_d = (RGB) diffuse reflection constant
|
||||||
|
let mut k_d = rgb;
|
||||||
|
// k_a = (RGB) ambient reflection constant
|
||||||
|
let mut k_a = rgb;
|
||||||
|
// α = (per-material) shininess constant
|
||||||
|
let mut alpha = 4.0; // 4.0;
|
||||||
|
|
||||||
|
// Compute connections
|
||||||
|
let mut has_river = false;
|
||||||
|
// NOTE: consider replacing neighbors with local_cells, since it is more
|
||||||
|
// accurate (though I'm not sure if it can matter for these
|
||||||
|
// purposes).
|
||||||
|
chunk_idx
|
||||||
|
.map(|chunk_idx| neighbors(map_size_lg, chunk_idx).chain(iter::once(chunk_idx)))
|
||||||
|
.into_iter()
|
||||||
|
.flatten()
|
||||||
|
.for_each(|neighbor_posi| {
|
||||||
|
let neighbor_pos = uniform_idx_as_vec2(map_size_lg, neighbor_posi);
|
||||||
|
let neighbor_wpos = neighbor_pos.map(|e| e as f64) * chunk_size;
|
||||||
|
let MapSample { connections, .. } = sample_pos(neighbor_pos);
|
||||||
|
NEIGHBOR_DELTA
|
||||||
|
.iter()
|
||||||
|
.zip(
|
||||||
|
connections
|
||||||
|
.as_ref()
|
||||||
|
.map(|e| e.iter())
|
||||||
|
.into_iter()
|
||||||
|
.flatten()
|
||||||
|
.into_iter(),
|
||||||
|
)
|
||||||
|
.for_each(|(&delta, connection)| {
|
||||||
|
let connection = if let Some(connection) = connection {
|
||||||
|
connection
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let downhill_wpos = neighbor_wpos
|
||||||
|
+ Vec2::from(delta).map(|e: i32| e as f64) * chunk_size;
|
||||||
|
let coeffs = river_spline_coeffs(
|
||||||
|
neighbor_wpos,
|
||||||
|
connection.spline_derivative,
|
||||||
|
downhill_wpos,
|
||||||
|
);
|
||||||
|
let (_t, _pt, dist) = if let Some((t, pt, dist)) =
|
||||||
|
quadratic_nearest_point(&coeffs, wposf)
|
||||||
|
{
|
||||||
|
(t, pt, dist)
|
||||||
|
} else {
|
||||||
|
let ndist = wposf.distance_squared(neighbor_wpos);
|
||||||
|
let ddist = wposf.distance_squared(downhill_wpos);
|
||||||
|
if ndist <= ddist {
|
||||||
|
(0.0, neighbor_wpos, ndist)
|
||||||
|
} else {
|
||||||
|
(1.0, downhill_wpos, ddist)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let connection_dist =
|
||||||
|
(dist.sqrt() - (connection.width as f64 * 0.5).max(1.0)).max(0.0);
|
||||||
|
if connection_dist == 0.0 {
|
||||||
|
match connection.kind {
|
||||||
|
ConnectionKind::River => {
|
||||||
|
has_river = true;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Color in connectins.
|
||||||
|
let water_color_factor = 2.0;
|
||||||
|
let g_water = 32.0 * water_color_factor;
|
||||||
|
let b_water = 64.0 * water_color_factor;
|
||||||
|
if has_river {
|
||||||
|
let water_rgb = Rgb::new(0, ((g_water) * 1.0) as u8, ((b_water) * 1.0) as u8)
|
||||||
|
.map(|e| e as f64 / 255.0);
|
||||||
|
rgb = water_rgb;
|
||||||
|
k_s = Rgb::new(1.0, 1.0, 1.0);
|
||||||
|
k_d = water_rgb;
|
||||||
|
k_a = water_rgb;
|
||||||
|
alpha = 0.255;
|
||||||
|
}
|
||||||
|
|
||||||
|
let downhill_alt = sample_wpos(downhill_wpos);
|
||||||
|
let cross_pos = wposi
|
||||||
|
+ ((downhill_wpos - wposi)
|
||||||
|
.map(|e| e as f32)
|
||||||
|
.rotated_z(f32::consts::FRAC_PI_2)
|
||||||
|
.map(|e| e as i32));
|
||||||
|
let cross_alt = sample_wpos(cross_pos);
|
||||||
|
// TODO: Fix use of fov to match proper perspective projection, as described in
|
||||||
|
// the doc comment.
|
||||||
|
// Pointing downhill, forward
|
||||||
|
// (index--note that (0,0,1) is backward right-handed)
|
||||||
|
let forward_vec = Vec3::new(
|
||||||
|
(downhill_wpos.x - wposi.x) as f64,
|
||||||
|
((downhill_alt - alt) * gain) as f64 * fov,
|
||||||
|
(downhill_wpos.y - wposi.y) 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 - wposi.x) as f64,
|
||||||
|
((cross_alt - alt) * gain) as f64 * fov,
|
||||||
|
(cross_pos.y - wposi.y) as f64,
|
||||||
|
);
|
||||||
|
// let surface_normal = Vec3::new(fov* (f.y * u.z - f.z * u.y), -(f.x * u.z -
|
||||||
|
// f.z * u.x), fov* (f.x * u.y - f.y * u.x)).normalized();
|
||||||
|
// 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();
|
||||||
|
|
||||||
|
// TODO: Figure out if we can reimplement debugging.
|
||||||
|
/* if is_debug {
|
||||||
|
let quad =
|
||||||
|
|x: f32| ((x as f64 * QUADRANTS as f64).floor() as usize).min(QUADRANTS - 1);
|
||||||
|
if river_kind.is_none() || humidity != 0.0 {
|
||||||
|
quads[quad(humidity)][quad(temperature)] += 1;
|
||||||
|
}
|
||||||
|
match river_kind {
|
||||||
|
Some(RiverKind::River { .. }) => {
|
||||||
|
rivers += 1;
|
||||||
|
},
|
||||||
|
Some(RiverKind::Lake { .. }) => {
|
||||||
|
lakes += 1;
|
||||||
|
},
|
||||||
|
Some(RiverKind::Ocean { .. }) => {
|
||||||
|
oceans += 1;
|
||||||
|
},
|
||||||
|
None => {},
|
||||||
|
}
|
||||||
|
} */
|
||||||
|
|
||||||
|
let shade_frac = horizon_map
|
||||||
|
.and_then(|(angles, heights)| {
|
||||||
|
chunk_idx
|
||||||
|
.and_then(|chunk_idx| angles.get(chunk_idx))
|
||||||
|
.map(|&e| (e as f64, heights))
|
||||||
|
})
|
||||||
|
.and_then(|(e, heights)| {
|
||||||
|
chunk_idx
|
||||||
|
.and_then(|chunk_idx| heights.get(chunk_idx))
|
||||||
|
.map(|&f| (e, f as f64))
|
||||||
|
})
|
||||||
|
.map(|(angle, height)| {
|
||||||
|
let w = 0.1;
|
||||||
|
let height = (height - f64::from(alt * gain)).max(0.0);
|
||||||
|
if angle != 0.0 && light_direction.x != 0.0 && height != 0.0 {
|
||||||
|
let deltax = height / angle;
|
||||||
|
let lighty = (light_direction.y / light_direction.x * deltax).abs();
|
||||||
|
let deltay = lighty - height;
|
||||||
|
let s = (deltay / deltax / w).min(1.0).max(0.0);
|
||||||
|
// Smoothstep
|
||||||
|
s * s * (3.0 - 2.0 * s)
|
||||||
|
} else {
|
||||||
|
1.0
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap_or(1.0);
|
||||||
|
|
||||||
|
let rgb = if is_shaded {
|
||||||
|
// Phong reflection model with shadows:
|
||||||
|
//
|
||||||
|
// I_p = k_a i_a + shadow * Σ {m ∈ lights} (k_d (L_m ⋅ N) i_m,d + k_s (R_m ⋅
|
||||||
|
// V)^α i_m,s)
|
||||||
|
//
|
||||||
|
// where for the whole scene,
|
||||||
|
// i_a = (RGB) intensity of ambient lighting component
|
||||||
|
let i_a = Rgb::new(0.1, 0.1, 0.1);
|
||||||
|
// V = direction pointing towards the viewer (e.g. virtual camera).
|
||||||
|
let v = Vec3::new(0.0, 0.0, -1.0).normalized();
|
||||||
|
// let v = Vec3::new(0.0, -1.0, 0.0).normalized();
|
||||||
|
//
|
||||||
|
// for each light m,
|
||||||
|
// i_m,d = (RGB) intensity of diffuse component of light source m
|
||||||
|
let i_m_d = Rgb::new(1.0, 1.0, 1.0);
|
||||||
|
// i_m,s = (RGB) intensity of specular component of light source m
|
||||||
|
let i_m_s = Rgb::new(0.45, 0.45, 0.45);
|
||||||
|
// let i_m_s = Rgb::new(0.45, 0.45, 0.45);
|
||||||
|
|
||||||
|
// for each light m and point p,
|
||||||
|
// L_m = (normalized) direction vector from point on surface to light source m
|
||||||
|
let l_m = light;
|
||||||
|
// N = (normalized) normal at this point on the surface,
|
||||||
|
let n = surface_normal;
|
||||||
|
// R_m = (normalized) direction a perfectly reflected ray of light from m would
|
||||||
|
// take from point p = 2(L_m ⋅ N)N - L_m
|
||||||
|
let r_m = (-l_m).reflected(n); // 2 * (l_m.dot(n)) * n - l_m;
|
||||||
|
//
|
||||||
|
// and for each point p in the scene,
|
||||||
|
// shadow = computed shadow factor at point p
|
||||||
|
// FIXME: Should really just be shade_frac, but with only ambient light we lose
|
||||||
|
// all local lighting detail... some sort of global illumination (e.g.
|
||||||
|
// radiosity) is of course the "right" solution, but maybe we can find
|
||||||
|
// something cheaper?
|
||||||
|
let shadow = 0.2 + 0.8 * shade_frac;
|
||||||
|
|
||||||
|
let lambertian = l_m.dot(n).max(0.0);
|
||||||
|
let spec_angle = r_m.dot(v).max(0.0);
|
||||||
|
|
||||||
|
let ambient = k_a * i_a;
|
||||||
|
let diffuse = k_d * lambertian * i_m_d;
|
||||||
|
let specular = k_s * spec_angle.powf(alpha) * i_m_s;
|
||||||
|
(ambient + shadow * (diffuse + specular)).map(|e| e.min(1.0))
|
||||||
|
} else {
|
||||||
|
rgb
|
||||||
|
}
|
||||||
|
.map(|e| (e * 255.0) as u8);
|
||||||
|
|
||||||
|
let rgba = (rgb.r, rgb.g, rgb.b, 255);
|
||||||
|
write_pixel(Vec2::new(i, j), rgba);
|
||||||
|
});
|
||||||
|
|
||||||
|
MapDebug {
|
||||||
|
quads,
|
||||||
|
rivers,
|
||||||
|
lakes,
|
||||||
|
oceans,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,14 +1,17 @@
|
|||||||
pub mod biome;
|
pub mod biome;
|
||||||
pub mod block;
|
pub mod block;
|
||||||
pub mod chonk;
|
pub mod chonk;
|
||||||
|
pub mod map;
|
||||||
pub mod structure;
|
pub mod structure;
|
||||||
|
|
||||||
// Reexports
|
// Reexports
|
||||||
pub use self::{
|
pub use self::{
|
||||||
biome::BiomeKind,
|
biome::BiomeKind,
|
||||||
block::{Block, BlockKind},
|
block::{Block, BlockKind},
|
||||||
|
map::MapSizeLg,
|
||||||
structure::Structure,
|
structure::Structure,
|
||||||
};
|
};
|
||||||
|
use roots::find_roots_cubic;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{vol::RectVolSize, volumes::vol_grid_2d::VolGrid2d};
|
use crate::{vol::RectVolSize, volumes::vol_grid_2d::VolGrid2d};
|
||||||
@ -19,8 +22,24 @@ use vek::*;
|
|||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct TerrainChunkSize;
|
pub struct TerrainChunkSize;
|
||||||
|
|
||||||
|
/// Base two logarithm of the number of blocks along either horizontal axis of
|
||||||
|
/// a chunk.
|
||||||
|
///
|
||||||
|
/// NOTE: (1 << CHUNK_SIZE_LG) is guaranteed to fit in a u32.
|
||||||
|
///
|
||||||
|
/// NOTE: A lot of code assumes that the two dimensions are equal, so we make it
|
||||||
|
/// explicit here.
|
||||||
|
///
|
||||||
|
/// NOTE: It is highly unlikely that a value greater than 5 will work, as many
|
||||||
|
/// frontend optimizations rely on being able to pack chunk horizontal
|
||||||
|
/// dimensions into 5 bits each.
|
||||||
|
pub const TERRAIN_CHUNK_BLOCKS_LG: u32 = 5;
|
||||||
|
|
||||||
impl RectVolSize for TerrainChunkSize {
|
impl RectVolSize for TerrainChunkSize {
|
||||||
const RECT_SIZE: Vec2<u32> = Vec2 { x: 32, y: 32 };
|
const RECT_SIZE: Vec2<u32> = Vec2 {
|
||||||
|
x: (1 << TERRAIN_CHUNK_BLOCKS_LG),
|
||||||
|
y: (1 << TERRAIN_CHUNK_BLOCKS_LG),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// TerrainChunkMeta
|
// TerrainChunkMeta
|
||||||
@ -50,3 +69,140 @@ impl TerrainChunkMeta {
|
|||||||
|
|
||||||
pub type TerrainChunk = chonk::Chonk<Block, TerrainChunkSize, TerrainChunkMeta>;
|
pub type TerrainChunk = chonk::Chonk<Block, TerrainChunkSize, TerrainChunkMeta>;
|
||||||
pub type TerrainGrid = VolGrid2d<TerrainChunk>;
|
pub type TerrainGrid = VolGrid2d<TerrainChunk>;
|
||||||
|
|
||||||
|
// Terrain helper functions used across multiple crates.
|
||||||
|
|
||||||
|
/// Computes the position Vec2 of a SimChunk from an index, where the index was
|
||||||
|
/// generated by uniform_noise.
|
||||||
|
///
|
||||||
|
/// NOTE: Dimensions obey constraints on [map::MapConfig::map_size_lg].
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn uniform_idx_as_vec2(map_size_lg: MapSizeLg, idx: usize) -> Vec2<i32> {
|
||||||
|
let x_mask = (1 << map_size_lg.vec().x) - 1;
|
||||||
|
Vec2::new((idx & x_mask) as i32, (idx >> map_size_lg.vec().x) as i32)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Computes the index of a Vec2 of a SimChunk from a position, where the index
|
||||||
|
/// is generated by uniform_noise. NOTE: Both components of idx should be
|
||||||
|
/// in-bounds!
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn vec2_as_uniform_idx(map_size_lg: MapSizeLg, idx: Vec2<i32>) -> usize {
|
||||||
|
((idx.y as usize) << map_size_lg.vec().x) | idx.x as usize
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: want to keep this such that the chunk index is in ascending order!
|
||||||
|
pub const NEIGHBOR_DELTA: [(i32, i32); 8] = [
|
||||||
|
(-1, -1),
|
||||||
|
(0, -1),
|
||||||
|
(1, -1),
|
||||||
|
(-1, 0),
|
||||||
|
(1, 0),
|
||||||
|
(-1, 1),
|
||||||
|
(0, 1),
|
||||||
|
(1, 1),
|
||||||
|
];
|
||||||
|
|
||||||
|
/// Iterate through all cells adjacent to a chunk.
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn neighbors(map_size_lg: MapSizeLg, posi: usize) -> impl Clone + Iterator<Item = usize> {
|
||||||
|
let pos = uniform_idx_as_vec2(map_size_lg, posi);
|
||||||
|
let world_size = map_size_lg.chunks();
|
||||||
|
NEIGHBOR_DELTA
|
||||||
|
.iter()
|
||||||
|
.map(move |&(x, y)| Vec2::new(pos.x + x, pos.y + y))
|
||||||
|
.filter(move |pos| {
|
||||||
|
pos.x >= 0 && pos.y >= 0 && pos.x < world_size.x as i32 && pos.y < world_size.y as i32
|
||||||
|
})
|
||||||
|
.map(move |pos| vec2_as_uniform_idx(map_size_lg, pos))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn river_spline_coeffs(
|
||||||
|
// _sim: &WorldSim,
|
||||||
|
chunk_pos: Vec2<f64>,
|
||||||
|
spline_derivative: Vec2<f32>,
|
||||||
|
downhill_pos: Vec2<f64>,
|
||||||
|
) -> Vec3<Vec2<f64>> {
|
||||||
|
let dxy = downhill_pos - chunk_pos;
|
||||||
|
// Since all splines have been precomputed, we don't have to do that much work
|
||||||
|
// to evaluate the spline. The spline is just ax^2 + bx + c = 0, where
|
||||||
|
//
|
||||||
|
// a = dxy - chunk.river.spline_derivative
|
||||||
|
// b = chunk.river.spline_derivative
|
||||||
|
// c = chunk_pos
|
||||||
|
let spline_derivative = spline_derivative.map(|e| e as f64);
|
||||||
|
Vec3::new(dxy - spline_derivative, spline_derivative, chunk_pos)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Find the nearest point from a quadratic spline to this point (in terms of t,
|
||||||
|
/// the "distance along the curve" by which our spline is parameterized). Note
|
||||||
|
/// that if t < 0.0 or t >= 1.0, we probably shouldn't be considered "on the
|
||||||
|
/// curve"... hopefully this works out okay and gives us what we want (a
|
||||||
|
/// river that extends outwards tangent to a quadratic curve, with width
|
||||||
|
/// configured by distance along the line).
|
||||||
|
#[allow(clippy::let_and_return)] // TODO: Pending review in #587
|
||||||
|
#[allow(clippy::many_single_char_names)]
|
||||||
|
pub fn quadratic_nearest_point(
|
||||||
|
spline: &Vec3<Vec2<f64>>,
|
||||||
|
point: Vec2<f64>,
|
||||||
|
) -> Option<(f64, Vec2<f64>, f64)> {
|
||||||
|
let a = spline.z.x;
|
||||||
|
let b = spline.y.x;
|
||||||
|
let c = spline.x.x;
|
||||||
|
let d = point.x;
|
||||||
|
let e = spline.z.y;
|
||||||
|
let f = spline.y.y;
|
||||||
|
let g = spline.x.y;
|
||||||
|
let h = point.y;
|
||||||
|
// This is equivalent to solving the following cubic equation (derivation is a
|
||||||
|
// bit annoying):
|
||||||
|
//
|
||||||
|
// A = 2(c^2 + g^2)
|
||||||
|
// B = 3(b * c + g * f)
|
||||||
|
// C = ((a - d) * 2 * c + b^2 + (e - h) * 2 * g + f^2)
|
||||||
|
// D = ((a - d) * b + (e - h) * f)
|
||||||
|
//
|
||||||
|
// Ax³ + Bx² + Cx + D = 0
|
||||||
|
//
|
||||||
|
// Once solved, this yield up to three possible values for t (reflecting minimal
|
||||||
|
// and maximal values). We should choose the minimal such real value with t
|
||||||
|
// between 0.0 and 1.0. If we fall outside those bounds, then we are
|
||||||
|
// outside the spline and return None.
|
||||||
|
let a_ = (c * c + g * g) * 2.0;
|
||||||
|
let b_ = (b * c + g * f) * 3.0;
|
||||||
|
let a_d = a - d;
|
||||||
|
let e_h = e - h;
|
||||||
|
let c_ = a_d * c * 2.0 + b * b + e_h * g * 2.0 + f * f;
|
||||||
|
let d_ = a_d * b + e_h * f;
|
||||||
|
let roots = find_roots_cubic(a_, b_, c_, d_);
|
||||||
|
let roots = roots.as_ref();
|
||||||
|
|
||||||
|
let min_root = roots
|
||||||
|
.iter()
|
||||||
|
.copied()
|
||||||
|
.filter_map(|root| {
|
||||||
|
let river_point = spline.x * root * root + spline.y * root + spline.z;
|
||||||
|
let river_zero = spline.z;
|
||||||
|
let river_one = spline.x + spline.y + spline.z;
|
||||||
|
if root > 0.0 && root < 1.0 {
|
||||||
|
Some((root, river_point))
|
||||||
|
} else if river_point.distance_squared(river_zero) < 0.5 {
|
||||||
|
Some((root, /*river_point*/ river_zero))
|
||||||
|
} else if river_point.distance_squared(river_one) < 0.5 {
|
||||||
|
Some((root, /*river_point*/ river_one))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.map(|(root, river_point)| {
|
||||||
|
let river_distance = river_point.distance_squared(point);
|
||||||
|
(root, river_point, river_distance)
|
||||||
|
})
|
||||||
|
// In the (unlikely?) case that distances are equal, prefer the earliest point along the
|
||||||
|
// river.
|
||||||
|
.min_by(|&(ap, _, a), &(bp, _, b)| {
|
||||||
|
(a, ap < 0.0 || ap > 1.0, ap)
|
||||||
|
.partial_cmp(&(b, bp < 0.0 || bp > 1.0, bp))
|
||||||
|
.unwrap()
|
||||||
|
});
|
||||||
|
min_root
|
||||||
|
}
|
||||||
|
@ -54,14 +54,14 @@ use std::{
|
|||||||
time::{Duration, Instant},
|
time::{Duration, Instant},
|
||||||
};
|
};
|
||||||
#[cfg(not(feature = "worldgen"))]
|
#[cfg(not(feature = "worldgen"))]
|
||||||
use test_world::{World, WORLD_SIZE};
|
use test_world::World;
|
||||||
use tracing::{debug, error, info, warn};
|
use tracing::{debug, error, info, warn};
|
||||||
use uvth::{ThreadPool, ThreadPoolBuilder};
|
use uvth::{ThreadPool, ThreadPoolBuilder};
|
||||||
use vek::*;
|
use vek::*;
|
||||||
#[cfg(feature = "worldgen")]
|
#[cfg(feature = "worldgen")]
|
||||||
use world::{
|
use world::{
|
||||||
civ::SiteKind,
|
civ::SiteKind,
|
||||||
sim::{FileOpts, WorldOpts, DEFAULT_WORLD_MAP, WORLD_SIZE},
|
sim::{FileOpts, WorldOpts, DEFAULT_WORLD_MAP},
|
||||||
World,
|
World,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -200,7 +200,7 @@ impl Server {
|
|||||||
// complaining)
|
// complaining)
|
||||||
|
|
||||||
// spawn in the chunk, that is in the middle of the world
|
// spawn in the chunk, that is in the middle of the world
|
||||||
let center_chunk: Vec2<i32> = WORLD_SIZE.map(|e| e as i32) / 2;
|
let center_chunk: Vec2<i32> = world.sim().map_size_lg().chunks().map(i32::from) / 2;
|
||||||
|
|
||||||
// Find a town to spawn in that's close to the centre of the world
|
// Find a town to spawn in that's close to the centre of the world
|
||||||
let spawn_chunk = world
|
let spawn_chunk = world
|
||||||
|
@ -1,13 +1,18 @@
|
|||||||
use common::{
|
use common::{
|
||||||
generation::{ChunkSupplement, EntityInfo, EntityKind},
|
generation::{ChunkSupplement, EntityInfo, EntityKind},
|
||||||
terrain::{Block, BlockKind, TerrainChunk, TerrainChunkMeta, TerrainChunkSize},
|
terrain::{Block, BlockKind, MapSizeLg, TerrainChunk, TerrainChunkMeta, TerrainChunkSize},
|
||||||
vol::{ReadVol, RectVolSize, Vox, WriteVol},
|
vol::{ReadVol, RectVolSize, Vox, WriteVol},
|
||||||
};
|
};
|
||||||
use rand::{prelude::*, rngs::SmallRng};
|
use rand::{prelude::*, rngs::SmallRng};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use vek::*;
|
use vek::*;
|
||||||
|
|
||||||
pub const WORLD_SIZE: Vec2<usize> = Vec2 { x: 1, y: 1 };
|
const DEFAULT_WORLD_CHUNKS_LG: MapSizeLg =
|
||||||
|
if let Ok(map_size_lg) = MapSizeLg::new(Vec2 { x: 1, y: 1 }) {
|
||||||
|
map_size_lg
|
||||||
|
} else {
|
||||||
|
panic!("Default world chunk size does not satisfy required invariants.");
|
||||||
|
};
|
||||||
|
|
||||||
pub struct World;
|
pub struct World;
|
||||||
|
|
||||||
@ -16,6 +21,9 @@ impl World {
|
|||||||
|
|
||||||
pub fn tick(&self, dt: Duration) {}
|
pub fn tick(&self, dt: Duration) {}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub const fn map_size_lg(&self) -> MapSizeLg { DEFAULT_WORLD_CHUNKS_LG }
|
||||||
|
|
||||||
pub fn generate_chunk(
|
pub fn generate_chunk(
|
||||||
&self,
|
&self,
|
||||||
chunk_pos: Vec2<i32>,
|
chunk_pos: Vec2<i32>,
|
||||||
|
@ -23,7 +23,6 @@ rand_chacha = "0.2.1"
|
|||||||
arr_macro = "0.1.2"
|
arr_macro = "0.1.2"
|
||||||
packed_simd = "0.3.3"
|
packed_simd = "0.3.3"
|
||||||
rayon = "^1.3.0"
|
rayon = "^1.3.0"
|
||||||
roots = "0.0.5"
|
|
||||||
serde = { version = "1.0.110", features = ["derive"] }
|
serde = { version = "1.0.110", features = ["derive"] }
|
||||||
ron = { version = "0.6", default-features = false }
|
ron = { version = "0.6", default-features = false }
|
||||||
|
|
||||||
|
@ -1,16 +1,19 @@
|
|||||||
use common::{terrain::TerrainChunkSize, vol::RectVolSize};
|
use common::{
|
||||||
|
terrain::{
|
||||||
|
map::{MapConfig, MapDebug, MapSample},
|
||||||
|
uniform_idx_as_vec2, vec2_as_uniform_idx, TerrainChunkSize,
|
||||||
|
},
|
||||||
|
vol::RectVolSize,
|
||||||
|
};
|
||||||
use rayon::prelude::*;
|
use rayon::prelude::*;
|
||||||
use std::{f64, io::Write, path::PathBuf, time::SystemTime};
|
use std::{f64, io::Write, path::PathBuf, time::SystemTime};
|
||||||
use tracing::warn;
|
use tracing::warn;
|
||||||
use tracing_subscriber;
|
use tracing_subscriber;
|
||||||
use vek::*;
|
use vek::*;
|
||||||
use veloren_world::{
|
use veloren_world::{
|
||||||
sim::{
|
sim::{self, get_horizon_map, sample_pos, sample_wpos, WorldOpts},
|
||||||
self, get_horizon_map, uniform_idx_as_vec2, vec2_as_uniform_idx, MapConfig, MapDebug,
|
|
||||||
MapSample, WorldOpts, WORLD_SIZE,
|
|
||||||
},
|
|
||||||
util::Sampler,
|
util::Sampler,
|
||||||
World, CONFIG,
|
ColumnSample, World, CONFIG,
|
||||||
};
|
};
|
||||||
|
|
||||||
const W: usize = 1024;
|
const W: usize = 1024;
|
||||||
@ -41,31 +44,41 @@ fn main() {
|
|||||||
});
|
});
|
||||||
tracing::info!("Sampling data...");
|
tracing::info!("Sampling data...");
|
||||||
let sampler = world.sim();
|
let sampler = world.sim();
|
||||||
|
let map_size_lg = sampler.map_size_lg();
|
||||||
|
|
||||||
let samples_data = {
|
let samples_data = {
|
||||||
let column_sample = world.sample_columns();
|
let column_sample = world.sample_columns();
|
||||||
(0..WORLD_SIZE.product())
|
(0..map_size_lg.chunks_len())
|
||||||
.into_par_iter()
|
.into_par_iter()
|
||||||
.map(|posi| {
|
.map(|posi| {
|
||||||
column_sample
|
column_sample.get(
|
||||||
.get(uniform_idx_as_vec2(posi) * TerrainChunkSize::RECT_SIZE.map(|e| e as i32))
|
uniform_idx_as_vec2(map_size_lg, posi)
|
||||||
|
* TerrainChunkSize::RECT_SIZE.map(|e| e as i32),
|
||||||
|
)
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.into_boxed_slice()
|
.into_boxed_slice()
|
||||||
};
|
};
|
||||||
let refresh_map_samples = |config: &MapConfig| {
|
let refresh_map_samples = |config: &MapConfig, samples: Option<&[Option<ColumnSample>]>| {
|
||||||
(0..WORLD_SIZE.product())
|
(0..map_size_lg.chunks_len())
|
||||||
.into_par_iter()
|
.into_par_iter()
|
||||||
.map(|posi| config.sample_pos(sampler, uniform_idx_as_vec2(posi)))
|
.map(|posi| {
|
||||||
|
sample_pos(
|
||||||
|
config,
|
||||||
|
sampler,
|
||||||
|
samples,
|
||||||
|
uniform_idx_as_vec2(map_size_lg, posi),
|
||||||
|
)
|
||||||
|
})
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.into_boxed_slice()
|
.into_boxed_slice()
|
||||||
};
|
};
|
||||||
let get_map_sample = |map_samples: &[MapSample], pos: Vec2<i32>| {
|
let get_map_sample = |map_samples: &[MapSample], pos: Vec2<i32>| {
|
||||||
if pos.reduce_partial_min() >= 0
|
if pos.reduce_partial_min() >= 0
|
||||||
&& pos.x < WORLD_SIZE.x as i32
|
&& pos.x < map_size_lg.chunks().x as i32
|
||||||
&& pos.y < WORLD_SIZE.y as i32
|
&& pos.y < map_size_lg.chunks().y as i32
|
||||||
{
|
{
|
||||||
map_samples[vec2_as_uniform_idx(pos)].clone()
|
map_samples[vec2_as_uniform_idx(map_size_lg, pos)].clone()
|
||||||
} else {
|
} else {
|
||||||
MapSample {
|
MapSample {
|
||||||
alt: 0.0,
|
alt: 0.0,
|
||||||
@ -76,26 +89,26 @@ fn main() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let refresh_horizons = |lgain, is_basement, is_water| {
|
let refresh_horizons = |is_basement, is_water| {
|
||||||
get_horizon_map(
|
get_horizon_map(
|
||||||
lgain,
|
map_size_lg,
|
||||||
Aabr {
|
Aabr {
|
||||||
min: Vec2::zero(),
|
min: Vec2::zero(),
|
||||||
max: WORLD_SIZE.map(|e| e as i32),
|
max: map_size_lg.chunks().map(|e| e as i32),
|
||||||
},
|
},
|
||||||
CONFIG.sea_level as f64,
|
CONFIG.sea_level,
|
||||||
(CONFIG.sea_level + sampler.max_height) as f64,
|
CONFIG.sea_level + sampler.max_height,
|
||||||
|posi| {
|
|posi| {
|
||||||
let sample = sampler.get(uniform_idx_as_vec2(posi)).unwrap();
|
let sample = sampler.get(uniform_idx_as_vec2(map_size_lg, posi)).unwrap();
|
||||||
if is_basement {
|
if is_basement {
|
||||||
sample.alt as f64
|
sample.alt
|
||||||
} else {
|
} else {
|
||||||
sample.basement as f64
|
sample.basement
|
||||||
}
|
}
|
||||||
.max(if is_water {
|
.max(if is_water {
|
||||||
sample.water_alt as f64
|
sample.water_alt
|
||||||
} else {
|
} else {
|
||||||
-f64::INFINITY
|
-f32::INFINITY
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|a| a,
|
|a| a,
|
||||||
@ -114,8 +127,8 @@ fn main() {
|
|||||||
// makes smaller differences in altitude appear larger.
|
// makes smaller differences in altitude appear larger.
|
||||||
let mut gain = /*CONFIG.mountain_scale*/sampler.max_height;
|
let mut gain = /*CONFIG.mountain_scale*/sampler.max_height;
|
||||||
// The Z component during normal calculations is multiplied by gain; thus,
|
// The Z component during normal calculations is multiplied by gain; thus,
|
||||||
let mut lgain = 1.0;
|
let mut fov = 1.0;
|
||||||
let mut scale = WORLD_SIZE.x as f64 / W as f64;
|
let mut scale = map_size_lg.chunks().x as f64 / W as f64;
|
||||||
|
|
||||||
// Right-handed coordinate system: light is going left, down, and "backwards"
|
// 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
|
// (i.e. on the map, where we translate the y coordinate on the world map to
|
||||||
@ -133,21 +146,21 @@ fn main() {
|
|||||||
let mut is_temperature = true;
|
let mut is_temperature = true;
|
||||||
let mut is_humidity = true;
|
let mut is_humidity = true;
|
||||||
|
|
||||||
let mut horizons = refresh_horizons(lgain, is_basement, is_water);
|
let mut horizons = refresh_horizons(is_basement, is_water);
|
||||||
let mut samples = None;
|
let mut samples = None;
|
||||||
|
|
||||||
let mut samples_changed = true;
|
let mut samples_changed = true;
|
||||||
let mut map_samples: Box<[_]> = Box::new([]);
|
let mut map_samples: Box<[_]> = Box::new([]);
|
||||||
while win.is_open() {
|
while win.is_open() {
|
||||||
let config = MapConfig {
|
let config = MapConfig {
|
||||||
|
map_size_lg,
|
||||||
dimensions: Vec2::new(W, H),
|
dimensions: Vec2::new(W, H),
|
||||||
focus,
|
focus,
|
||||||
gain,
|
gain,
|
||||||
lgain,
|
fov,
|
||||||
scale,
|
scale,
|
||||||
light_direction,
|
light_direction,
|
||||||
horizons: horizons.as_ref(), /* .map(|(a, b)| (&**a, &**b)) */
|
horizons: horizons.as_ref(), /* .map(|(a, b)| (&**a, &**b)) */
|
||||||
samples,
|
|
||||||
|
|
||||||
is_basement,
|
is_basement,
|
||||||
is_water,
|
is_water,
|
||||||
@ -158,7 +171,7 @@ fn main() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if samples_changed {
|
if samples_changed {
|
||||||
map_samples = refresh_map_samples(&config);
|
map_samples = refresh_map_samples(&config, samples);
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut buf = vec![0; W * H];
|
let mut buf = vec![0; W * H];
|
||||||
@ -169,7 +182,7 @@ fn main() {
|
|||||||
quads,
|
quads,
|
||||||
} = config.generate(
|
} = config.generate(
|
||||||
|pos| get_map_sample(&map_samples, pos),
|
|pos| get_map_sample(&map_samples, pos),
|
||||||
|pos| config.sample_wpos(sampler, pos),
|
|pos| sample_wpos(&config, sampler, pos),
|
||||||
|pos, (r, g, b, a)| {
|
|pos, (r, g, b, a)| {
|
||||||
let i = pos.x;
|
let i = pos.x;
|
||||||
let j = pos.y;
|
let j = pos.y;
|
||||||
@ -187,7 +200,7 @@ fn main() {
|
|||||||
{
|
{
|
||||||
let x = (W as f64 * scale) as usize;
|
let x = (W as f64 * scale) as usize;
|
||||||
let y = (H as f64 * scale) as usize;
|
let y = (H as f64 * scale) as usize;
|
||||||
let config = sim::MapConfig {
|
let config = MapConfig {
|
||||||
dimensions: Vec2::new(x, y),
|
dimensions: Vec2::new(x, y),
|
||||||
scale: 1.0,
|
scale: 1.0,
|
||||||
..config
|
..config
|
||||||
@ -195,7 +208,7 @@ fn main() {
|
|||||||
let mut buf = vec![0u8; 4 * len];
|
let mut buf = vec![0u8; 4 * len];
|
||||||
config.generate(
|
config.generate(
|
||||||
|pos| get_map_sample(&map_samples, pos),
|
|pos| get_map_sample(&map_samples, pos),
|
||||||
|pos| config.sample_wpos(sampler, pos),
|
|pos| sample_wpos(&config, sampler, pos),
|
||||||
|pos, (r, g, b, a)| {
|
|pos, (r, g, b, a)| {
|
||||||
let i = pos.x;
|
let i = pos.x;
|
||||||
let j = pos.y;
|
let j = pos.y;
|
||||||
@ -233,7 +246,7 @@ fn main() {
|
|||||||
Land(adjacent): (X = temp, Y = humidity): {:?}\nRivers: {:?}\nLakes: \
|
Land(adjacent): (X = temp, Y = humidity): {:?}\nRivers: {:?}\nLakes: \
|
||||||
{:?}\nOceans: {:?}\nTotal water: {:?}\nTotal land(adjacent): {:?}",
|
{:?}\nOceans: {:?}\nTotal water: {:?}\nTotal land(adjacent): {:?}",
|
||||||
gain,
|
gain,
|
||||||
lgain,
|
fov,
|
||||||
scale,
|
scale,
|
||||||
focus,
|
focus,
|
||||||
light_direction,
|
light_direction,
|
||||||
@ -272,7 +285,7 @@ fn main() {
|
|||||||
if win.is_key_down(minifb::Key::B) {
|
if win.is_key_down(minifb::Key::B) {
|
||||||
is_basement ^= true;
|
is_basement ^= true;
|
||||||
samples_changed = true;
|
samples_changed = true;
|
||||||
horizons = horizons.and_then(|_| refresh_horizons(lgain, is_basement, is_water));
|
horizons = horizons.and_then(|_| refresh_horizons(is_basement, is_water));
|
||||||
}
|
}
|
||||||
if win.is_key_down(minifb::Key::H) {
|
if win.is_key_down(minifb::Key::H) {
|
||||||
is_humidity ^= true;
|
is_humidity ^= true;
|
||||||
@ -285,7 +298,7 @@ fn main() {
|
|||||||
if win.is_key_down(minifb::Key::O) {
|
if win.is_key_down(minifb::Key::O) {
|
||||||
is_water ^= true;
|
is_water ^= true;
|
||||||
samples_changed = true;
|
samples_changed = true;
|
||||||
horizons = horizons.and_then(|_| refresh_horizons(lgain, is_basement, is_water));
|
horizons = horizons.and_then(|_| refresh_horizons(is_basement, is_water));
|
||||||
}
|
}
|
||||||
if win.is_key_down(minifb::Key::L) {
|
if win.is_key_down(minifb::Key::L) {
|
||||||
if is_camera {
|
if is_camera {
|
||||||
@ -293,7 +306,7 @@ fn main() {
|
|||||||
horizons = if horizons.is_some() {
|
horizons = if horizons.is_some() {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
refresh_horizons(lgain, is_basement, is_water)
|
refresh_horizons(is_basement, is_water)
|
||||||
};
|
};
|
||||||
samples_changed = true;
|
samples_changed = true;
|
||||||
} else {
|
} else {
|
||||||
@ -335,10 +348,8 @@ fn main() {
|
|||||||
}
|
}
|
||||||
if win.is_key_down(minifb::Key::Q) {
|
if win.is_key_down(minifb::Key::Q) {
|
||||||
if is_camera {
|
if is_camera {
|
||||||
if (lgain * 2.0).is_normal() {
|
if (fov * 2.0).is_normal() {
|
||||||
lgain *= 2.0;
|
fov *= 2.0;
|
||||||
horizons =
|
|
||||||
horizons.and_then(|_| refresh_horizons(lgain, is_basement, is_water));
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
gain += 64.0;
|
gain += 64.0;
|
||||||
@ -346,10 +357,8 @@ fn main() {
|
|||||||
}
|
}
|
||||||
if win.is_key_down(minifb::Key::E) {
|
if win.is_key_down(minifb::Key::E) {
|
||||||
if is_camera {
|
if is_camera {
|
||||||
if (lgain / 2.0).is_normal() {
|
if (fov / 2.0).is_normal() {
|
||||||
lgain /= 2.0;
|
fov /= 2.0;
|
||||||
horizons =
|
|
||||||
horizons.and_then(|_| refresh_horizons(lgain, is_basement, is_water));
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
gain = (gain - 64.0).max(64.0);
|
gain = (gain - 64.0).max(64.0);
|
||||||
|
@ -14,7 +14,7 @@ use common::{
|
|||||||
path::Path,
|
path::Path,
|
||||||
spiral::Spiral2d,
|
spiral::Spiral2d,
|
||||||
store::{Id, Store},
|
store::{Id, Store},
|
||||||
terrain::TerrainChunkSize,
|
terrain::{MapSizeLg, TerrainChunkSize},
|
||||||
vol::RectVolSize,
|
vol::RectVolSize,
|
||||||
};
|
};
|
||||||
use core::{
|
use core::{
|
||||||
@ -29,7 +29,11 @@ use rand_chacha::ChaChaRng;
|
|||||||
use tracing::{debug, info, warn};
|
use tracing::{debug, info, warn};
|
||||||
use vek::*;
|
use vek::*;
|
||||||
|
|
||||||
const INITIAL_CIV_COUNT: usize = (crate::sim::WORLD_SIZE.x * crate::sim::WORLD_SIZE.y * 3) / 65536; //48 at default scale
|
const fn initial_civ_count(map_size_lg: MapSizeLg) -> u32 {
|
||||||
|
// NOTE: since map_size_lg's dimensions must fit in a u16, we can safely add
|
||||||
|
// them here. NOTE: N48 at "default" scale of 10 × 10 bits.
|
||||||
|
(3 << (map_size_lg.vec().x + map_size_lg.vec().y)) >> 16
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(clippy::type_complexity)] // TODO: Pending review in #587
|
#[allow(clippy::type_complexity)] // TODO: Pending review in #587
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
@ -74,17 +78,17 @@ impl Civs {
|
|||||||
pub fn generate(seed: u32, sim: &mut WorldSim) -> Self {
|
pub fn generate(seed: u32, sim: &mut WorldSim) -> Self {
|
||||||
let mut this = Self::default();
|
let mut this = Self::default();
|
||||||
let rng = ChaChaRng::from_seed(seed_expan::rng_state(seed));
|
let rng = ChaChaRng::from_seed(seed_expan::rng_state(seed));
|
||||||
|
let initial_civ_count = initial_civ_count(sim.map_size_lg());
|
||||||
let mut ctx = GenCtx { sim, rng };
|
let mut ctx = GenCtx { sim, rng };
|
||||||
|
for _ in 0..initial_civ_count {
|
||||||
for _ in 0..INITIAL_CIV_COUNT {
|
|
||||||
debug!("Creating civilisation...");
|
debug!("Creating civilisation...");
|
||||||
if this.birth_civ(&mut ctx.reseed()).is_none() {
|
if this.birth_civ(&mut ctx.reseed()).is_none() {
|
||||||
warn!("Failed to find starting site for civilisation.");
|
warn!("Failed to find starting site for civilisation.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
info!(?INITIAL_CIV_COUNT, "all civilisations created");
|
info!(?initial_civ_count, "all civilisations created");
|
||||||
|
|
||||||
for _ in 0..INITIAL_CIV_COUNT * 3 {
|
for _ in 0..initial_civ_count * 3 {
|
||||||
attempt(5, || {
|
attempt(5, || {
|
||||||
let loc = find_site_loc(&mut ctx, None)?;
|
let loc = find_site_loc(&mut ctx, None)?;
|
||||||
this.establish_site(&mut ctx.reseed(), loc, |place| Site {
|
this.establish_site(&mut ctx.reseed(), loc, |place| Site {
|
||||||
|
@ -1,13 +1,18 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
all::ForestKind,
|
all::ForestKind,
|
||||||
block::StructureMeta,
|
block::StructureMeta,
|
||||||
sim::{local_cells, uniform_idx_as_vec2, vec2_as_uniform_idx, RiverKind, SimChunk, WorldSim},
|
sim::{local_cells, RiverKind, SimChunk, WorldSim},
|
||||||
util::Sampler,
|
util::Sampler,
|
||||||
CONFIG,
|
CONFIG,
|
||||||
};
|
};
|
||||||
use common::{terrain::TerrainChunkSize, vol::RectVolSize};
|
use common::{
|
||||||
|
terrain::{
|
||||||
|
quadratic_nearest_point, river_spline_coeffs, uniform_idx_as_vec2, vec2_as_uniform_idx,
|
||||||
|
TerrainChunkSize,
|
||||||
|
},
|
||||||
|
vol::RectVolSize,
|
||||||
|
};
|
||||||
use noise::NoiseFn;
|
use noise::NoiseFn;
|
||||||
use roots::find_roots_cubic;
|
|
||||||
use std::{
|
use std::{
|
||||||
cmp::Reverse,
|
cmp::Reverse,
|
||||||
f32, f64,
|
f32, f64,
|
||||||
@ -74,97 +79,6 @@ impl<'a> ColumnGen<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn river_spline_coeffs(
|
|
||||||
// _sim: &WorldSim,
|
|
||||||
chunk_pos: Vec2<f64>,
|
|
||||||
spline_derivative: Vec2<f32>,
|
|
||||||
downhill_pos: Vec2<f64>,
|
|
||||||
) -> Vec3<Vec2<f64>> {
|
|
||||||
let dxy = downhill_pos - chunk_pos;
|
|
||||||
// Since all splines have been precomputed, we don't have to do that much work
|
|
||||||
// to evaluate the spline. The spline is just ax^2 + bx + c = 0, where
|
|
||||||
//
|
|
||||||
// a = dxy - chunk.river.spline_derivative
|
|
||||||
// b = chunk.river.spline_derivative
|
|
||||||
// c = chunk_pos
|
|
||||||
let spline_derivative = spline_derivative.map(|e| e as f64);
|
|
||||||
Vec3::new(dxy - spline_derivative, spline_derivative, chunk_pos)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Find the nearest point from a quadratic spline to this point (in terms of t,
|
|
||||||
/// the "distance along the curve" by which our spline is parameterized). Note
|
|
||||||
/// that if t < 0.0 or t >= 1.0, we probably shouldn't be considered "on the
|
|
||||||
/// curve"... hopefully this works out okay and gives us what we want (a
|
|
||||||
/// river that extends outwards tangent to a quadratic curve, with width
|
|
||||||
/// configured by distance along the line).
|
|
||||||
#[allow(clippy::let_and_return)] // TODO: Pending review in #587
|
|
||||||
#[allow(clippy::many_single_char_names)]
|
|
||||||
pub fn quadratic_nearest_point(
|
|
||||||
spline: &Vec3<Vec2<f64>>,
|
|
||||||
point: Vec2<f64>,
|
|
||||||
) -> Option<(f64, Vec2<f64>, f64)> {
|
|
||||||
let a = spline.z.x;
|
|
||||||
let b = spline.y.x;
|
|
||||||
let c = spline.x.x;
|
|
||||||
let d = point.x;
|
|
||||||
let e = spline.z.y;
|
|
||||||
let f = spline.y.y;
|
|
||||||
let g = spline.x.y;
|
|
||||||
let h = point.y;
|
|
||||||
// This is equivalent to solving the following cubic equation (derivation is a
|
|
||||||
// bit annoying):
|
|
||||||
//
|
|
||||||
// A = 2(c^2 + g^2)
|
|
||||||
// B = 3(b * c + g * f)
|
|
||||||
// C = ((a - d) * 2 * c + b^2 + (e - h) * 2 * g + f^2)
|
|
||||||
// D = ((a - d) * b + (e - h) * f)
|
|
||||||
//
|
|
||||||
// Ax³ + Bx² + Cx + D = 0
|
|
||||||
//
|
|
||||||
// Once solved, this yield up to three possible values for t (reflecting minimal
|
|
||||||
// and maximal values). We should choose the minimal such real value with t
|
|
||||||
// between 0.0 and 1.0. If we fall outside those bounds, then we are
|
|
||||||
// outside the spline and return None.
|
|
||||||
let a_ = (c * c + g * g) * 2.0;
|
|
||||||
let b_ = (b * c + g * f) * 3.0;
|
|
||||||
let a_d = a - d;
|
|
||||||
let e_h = e - h;
|
|
||||||
let c_ = a_d * c * 2.0 + b * b + e_h * g * 2.0 + f * f;
|
|
||||||
let d_ = a_d * b + e_h * f;
|
|
||||||
let roots = find_roots_cubic(a_, b_, c_, d_);
|
|
||||||
let roots = roots.as_ref();
|
|
||||||
|
|
||||||
let min_root = roots
|
|
||||||
.iter()
|
|
||||||
.copied()
|
|
||||||
.filter_map(|root| {
|
|
||||||
let river_point = spline.x * root * root + spline.y * root + spline.z;
|
|
||||||
let river_zero = spline.z;
|
|
||||||
let river_one = spline.x + spline.y + spline.z;
|
|
||||||
if root > 0.0 && root < 1.0 {
|
|
||||||
Some((root, river_point))
|
|
||||||
} else if river_point.distance_squared(river_zero) < 0.5 {
|
|
||||||
Some((root, /*river_point*/ river_zero))
|
|
||||||
} else if river_point.distance_squared(river_one) < 0.5 {
|
|
||||||
Some((root, /*river_point*/ river_one))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.map(|(root, river_point)| {
|
|
||||||
let river_distance = river_point.distance_squared(point);
|
|
||||||
(root, river_point, river_distance)
|
|
||||||
})
|
|
||||||
// In the (unlikely?) case that distances are equal, prefer the earliest point along the
|
|
||||||
// river.
|
|
||||||
.min_by(|&(ap, _, a), &(bp, _, b)| {
|
|
||||||
(a, ap < 0.0 || ap > 1.0, ap)
|
|
||||||
.partial_cmp(&(b, bp < 0.0 || bp > 1.0, bp))
|
|
||||||
.unwrap()
|
|
||||||
});
|
|
||||||
min_root
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Sampler<'a> for ColumnGen<'a> {
|
impl<'a> Sampler<'a> for ColumnGen<'a> {
|
||||||
type Index = Vec2<i32>;
|
type Index = Vec2<i32>;
|
||||||
type Sample = Option<ColumnSample<'a>>;
|
type Sample = Option<ColumnSample<'a>>;
|
||||||
@ -196,12 +110,13 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
|
|||||||
let chunk_warp_factor = sim.get_interpolated_monotone(wpos, |chunk| chunk.warp_factor)?;
|
let chunk_warp_factor = sim.get_interpolated_monotone(wpos, |chunk| chunk.warp_factor)?;
|
||||||
let sim_chunk = sim.get(chunk_pos)?;
|
let sim_chunk = sim.get(chunk_pos)?;
|
||||||
let neighbor_coef = TerrainChunkSize::RECT_SIZE.map(|e| e as f64);
|
let neighbor_coef = TerrainChunkSize::RECT_SIZE.map(|e| e as f64);
|
||||||
let my_chunk_idx = vec2_as_uniform_idx(chunk_pos);
|
let my_chunk_idx = vec2_as_uniform_idx(self.sim.map_size_lg(), chunk_pos);
|
||||||
let neighbor_river_data = local_cells(my_chunk_idx).filter_map(|neighbor_idx: usize| {
|
let neighbor_river_data =
|
||||||
let neighbor_pos = uniform_idx_as_vec2(neighbor_idx);
|
local_cells(self.sim.map_size_lg(), my_chunk_idx).filter_map(|neighbor_idx: usize| {
|
||||||
let neighbor_chunk = sim.get(neighbor_pos)?;
|
let neighbor_pos = uniform_idx_as_vec2(self.sim.map_size_lg(), neighbor_idx);
|
||||||
Some((neighbor_pos, neighbor_chunk, &neighbor_chunk.river))
|
let neighbor_chunk = sim.get(neighbor_pos)?;
|
||||||
});
|
Some((neighbor_pos, neighbor_chunk, &neighbor_chunk.river))
|
||||||
|
});
|
||||||
let lake_width = (TerrainChunkSize::RECT_SIZE.x as f64 * (2.0f64.sqrt())) + 12.0;
|
let lake_width = (TerrainChunkSize::RECT_SIZE.x as f64 * (2.0f64.sqrt())) + 12.0;
|
||||||
let neighbor_river_data = neighbor_river_data.map(|(posj, chunkj, river)| {
|
let neighbor_river_data = neighbor_river_data.map(|(posj, chunkj, river)| {
|
||||||
let kind = match river.river_kind {
|
let kind = match river.river_kind {
|
||||||
|
@ -1,7 +1,13 @@
|
|||||||
#![deny(unsafe_code)]
|
#![deny(unsafe_code)]
|
||||||
#![allow(incomplete_features)]
|
#![allow(incomplete_features)]
|
||||||
#![allow(clippy::option_map_unit_fn)]
|
#![allow(clippy::option_map_unit_fn)]
|
||||||
#![feature(arbitrary_enum_discriminant, const_generics, label_break_value)]
|
#![feature(
|
||||||
|
arbitrary_enum_discriminant,
|
||||||
|
const_if_match,
|
||||||
|
const_generics,
|
||||||
|
const_panic,
|
||||||
|
label_break_value
|
||||||
|
)]
|
||||||
|
|
||||||
mod all;
|
mod all;
|
||||||
mod block;
|
mod block;
|
||||||
@ -16,9 +22,10 @@ pub mod util;
|
|||||||
// Reexports
|
// Reexports
|
||||||
pub use crate::config::CONFIG;
|
pub use crate::config::CONFIG;
|
||||||
pub use block::BlockGen;
|
pub use block::BlockGen;
|
||||||
|
pub use column::ColumnSample;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
column::{ColumnGen, ColumnSample},
|
column::ColumnGen,
|
||||||
util::{Grid, Sampler},
|
util::{Grid, Sampler},
|
||||||
};
|
};
|
||||||
use common::{
|
use common::{
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
use super::{
|
use super::{diffusion, downhill, uphill};
|
||||||
diffusion, downhill, neighbors, uniform_idx_as_vec2, uphill, vec2_as_uniform_idx,
|
|
||||||
NEIGHBOR_DELTA, WORLD_SIZE,
|
|
||||||
};
|
|
||||||
use crate::{config::CONFIG, util::RandomField};
|
use crate::{config::CONFIG, util::RandomField};
|
||||||
use common::{terrain::TerrainChunkSize, vol::RectVolSize};
|
use common::{
|
||||||
|
terrain::{
|
||||||
|
neighbors, uniform_idx_as_vec2, vec2_as_uniform_idx, MapSizeLg, TerrainChunkSize,
|
||||||
|
NEIGHBOR_DELTA,
|
||||||
|
},
|
||||||
|
vol::RectVolSize,
|
||||||
|
};
|
||||||
use tracing::{debug, error, warn};
|
use tracing::{debug, error, warn};
|
||||||
// use faster::*;
|
// use faster::*;
|
||||||
use itertools::izip;
|
use itertools::izip;
|
||||||
@ -27,7 +30,12 @@ pub type Computex8 = [Compute; 8];
|
|||||||
|
|
||||||
/// Compute the water flux at all chunks, given a list of chunk indices sorted
|
/// Compute the water flux at all chunks, given a list of chunk indices sorted
|
||||||
/// by increasing height.
|
/// by increasing height.
|
||||||
pub fn get_drainage(newh: &[u32], downhill: &[isize], _boundary_len: usize) -> Box<[f32]> {
|
pub fn get_drainage(
|
||||||
|
map_size_lg: MapSizeLg,
|
||||||
|
newh: &[u32],
|
||||||
|
downhill: &[isize],
|
||||||
|
_boundary_len: usize,
|
||||||
|
) -> Box<[f32]> {
|
||||||
// FIXME: Make the below work. For now, we just use constant flux.
|
// FIXME: Make the below work. For now, we just use constant flux.
|
||||||
// Initially, flux is determined by rainfall. We currently treat this as the
|
// Initially, flux is determined by rainfall. We currently treat this as the
|
||||||
// same as humidity, so we just use humidity as a proxy. The total flux
|
// same as humidity, so we just use humidity as a proxy. The total flux
|
||||||
@ -35,10 +43,10 @@ pub fn get_drainage(newh: &[u32], downhill: &[isize], _boundary_len: usize) -> B
|
|||||||
// to be 0.5. To figure out how far from normal any given chunk is, we use
|
// to be 0.5. To figure out how far from normal any given chunk is, we use
|
||||||
// its logit. NOTE: If there are no non-boundary chunks, we just set
|
// its logit. NOTE: If there are no non-boundary chunks, we just set
|
||||||
// base_flux to 1.0; this should still work fine because in that case
|
// base_flux to 1.0; this should still work fine because in that case
|
||||||
// there's no erosion anyway. let base_flux = 1.0 / ((WORLD_SIZE.x *
|
// there's no erosion anyway. let base_flux = 1.0 / ((map_size_lg.chunks_len())
|
||||||
// WORLD_SIZE.y) as f32);
|
// as f32);
|
||||||
let base_flux = 1.0;
|
let base_flux = 1.0;
|
||||||
let mut flux = vec![base_flux; WORLD_SIZE.x * WORLD_SIZE.y].into_boxed_slice();
|
let mut flux = vec![base_flux; map_size_lg.chunks_len()].into_boxed_slice();
|
||||||
newh.iter().rev().for_each(|&chunk_idx| {
|
newh.iter().rev().for_each(|&chunk_idx| {
|
||||||
let chunk_idx = chunk_idx as usize;
|
let chunk_idx = chunk_idx as usize;
|
||||||
let downhill_idx = downhill[chunk_idx];
|
let downhill_idx = downhill[chunk_idx];
|
||||||
@ -52,6 +60,7 @@ pub fn get_drainage(newh: &[u32], downhill: &[isize], _boundary_len: usize) -> B
|
|||||||
/// Compute the water flux at all chunks for multiple receivers, given a list of
|
/// Compute the water flux at all chunks for multiple receivers, given a list of
|
||||||
/// chunk indices sorted by increasing height and weights for each receiver.
|
/// chunk indices sorted by increasing height and weights for each receiver.
|
||||||
pub fn get_multi_drainage(
|
pub fn get_multi_drainage(
|
||||||
|
map_size_lg: MapSizeLg,
|
||||||
mstack: &[u32],
|
mstack: &[u32],
|
||||||
mrec: &[u8],
|
mrec: &[u8],
|
||||||
mwrec: &[Computex8],
|
mwrec: &[Computex8],
|
||||||
@ -66,12 +75,12 @@ pub fn get_multi_drainage(
|
|||||||
// base_flux to 1.0; this should still work fine because in that case
|
// base_flux to 1.0; this should still work fine because in that case
|
||||||
// there's no erosion anyway.
|
// there's no erosion anyway.
|
||||||
let base_area = 1.0;
|
let base_area = 1.0;
|
||||||
let mut area = vec![base_area; WORLD_SIZE.x * WORLD_SIZE.y].into_boxed_slice();
|
let mut area = vec![base_area; map_size_lg.chunks_len()].into_boxed_slice();
|
||||||
mstack.iter().for_each(|&ij| {
|
mstack.iter().for_each(|&ij| {
|
||||||
let ij = ij as usize;
|
let ij = ij as usize;
|
||||||
let wrec_ij = &mwrec[ij];
|
let wrec_ij = &mwrec[ij];
|
||||||
let area_ij = area[ij];
|
let area_ij = area[ij];
|
||||||
mrec_downhill(mrec, ij).for_each(|(k, ijr)| {
|
mrec_downhill(map_size_lg, mrec, ij).for_each(|(k, ijr)| {
|
||||||
area[ijr] += area_ij * wrec_ij[k];
|
area[ijr] += area_ij * wrec_ij[k];
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -226,6 +235,7 @@ impl RiverData {
|
|||||||
/// liberties with the constant factors etc. in order to make it more likely
|
/// liberties with the constant factors etc. in order to make it more likely
|
||||||
/// that we draw rivers at all.
|
/// that we draw rivers at all.
|
||||||
pub fn get_rivers<F: fmt::Debug + Float + Into<f64>, G: Float + Into<f64>>(
|
pub fn get_rivers<F: fmt::Debug + Float + Into<f64>, G: Float + Into<f64>>(
|
||||||
|
map_size_lg: MapSizeLg,
|
||||||
newh: &[u32],
|
newh: &[u32],
|
||||||
water_alt: &[F],
|
water_alt: &[F],
|
||||||
downhill: &[isize],
|
downhill: &[isize],
|
||||||
@ -236,7 +246,7 @@ pub fn get_rivers<F: fmt::Debug + Float + Into<f64>, G: Float + Into<f64>>(
|
|||||||
// to build up the derivatives from the top down. Fortunately this
|
// to build up the derivatives from the top down. Fortunately this
|
||||||
// computation seems tractable.
|
// computation seems tractable.
|
||||||
|
|
||||||
let mut rivers = vec![RiverData::default(); WORLD_SIZE.x * WORLD_SIZE.y].into_boxed_slice();
|
let mut rivers = vec![RiverData::default(); map_size_lg.chunks_len()].into_boxed_slice();
|
||||||
let neighbor_coef = TerrainChunkSize::RECT_SIZE.map(|e| e as f64);
|
let neighbor_coef = TerrainChunkSize::RECT_SIZE.map(|e| e as f64);
|
||||||
// (Roughly) area of a chunk, times minutes per second.
|
// (Roughly) area of a chunk, times minutes per second.
|
||||||
// NOTE: Clearly, this should "actually" be 1/60 (or maybe 1/64, if you want to
|
// NOTE: Clearly, this should "actually" be 1/60 (or maybe 1/64, if you want to
|
||||||
@ -262,8 +272,8 @@ pub fn get_rivers<F: fmt::Debug + Float + Into<f64>, G: Float + Into<f64>>(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let downhill_idx = downhill_idx as usize;
|
let downhill_idx = downhill_idx as usize;
|
||||||
let downhill_pos = uniform_idx_as_vec2(downhill_idx);
|
let downhill_pos = uniform_idx_as_vec2(map_size_lg, downhill_idx);
|
||||||
let dxy = (downhill_pos - uniform_idx_as_vec2(chunk_idx)).map(|e| e as f64);
|
let dxy = (downhill_pos - uniform_idx_as_vec2(map_size_lg, chunk_idx)).map(|e| e as f64);
|
||||||
let neighbor_dim = neighbor_coef * dxy;
|
let neighbor_dim = neighbor_coef * dxy;
|
||||||
// First, we calculate the river's volumetric flow rate.
|
// First, we calculate the river's volumetric flow rate.
|
||||||
let chunk_drainage = drainage[chunk_idx].into();
|
let chunk_drainage = drainage[chunk_idx].into();
|
||||||
@ -382,10 +392,16 @@ pub fn get_rivers<F: fmt::Debug + Float + Into<f64>, G: Float + Into<f64>>(
|
|||||||
// This is a pass, so set our flow direction to point to the neighbor pass
|
// This is a pass, so set our flow direction to point to the neighbor pass
|
||||||
// rather than downhill.
|
// rather than downhill.
|
||||||
// NOTE: Safe because neighbor_pass_idx >= 0.
|
// NOTE: Safe because neighbor_pass_idx >= 0.
|
||||||
(uniform_idx_as_vec2(downhill_idx), river_spline_derivative)
|
(
|
||||||
|
uniform_idx_as_vec2(map_size_lg, downhill_idx),
|
||||||
|
river_spline_derivative,
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
// Try pointing towards the lake side of the pass.
|
// Try pointing towards the lake side of the pass.
|
||||||
(uniform_idx_as_vec2(pass_idx), river_spline_derivative)
|
(
|
||||||
|
uniform_idx_as_vec2(map_size_lg, pass_idx),
|
||||||
|
river_spline_derivative,
|
||||||
|
)
|
||||||
};
|
};
|
||||||
let mut lake = &mut rivers[chunk_idx];
|
let mut lake = &mut rivers[chunk_idx];
|
||||||
lake.spline_derivative = river_spline_derivative;
|
lake.spline_derivative = river_spline_derivative;
|
||||||
@ -427,12 +443,12 @@ pub fn get_rivers<F: fmt::Debug + Float + Into<f64>, G: Float + Into<f64>>(
|
|||||||
error!(
|
error!(
|
||||||
"Our chunk (and downhill, lake, pass, neighbor_pass): {:?} (to {:?}, in {:?} via \
|
"Our chunk (and downhill, lake, pass, neighbor_pass): {:?} (to {:?}, in {:?} via \
|
||||||
{:?} to {:?}), chunk water alt: {:?}, lake water alt: {:?}",
|
{:?} to {:?}), chunk water alt: {:?}, lake water alt: {:?}",
|
||||||
uniform_idx_as_vec2(chunk_idx),
|
uniform_idx_as_vec2(map_size_lg, chunk_idx),
|
||||||
uniform_idx_as_vec2(downhill_idx),
|
uniform_idx_as_vec2(map_size_lg, downhill_idx),
|
||||||
uniform_idx_as_vec2(lake_idx),
|
uniform_idx_as_vec2(map_size_lg, lake_idx),
|
||||||
uniform_idx_as_vec2(pass_idx),
|
uniform_idx_as_vec2(map_size_lg, pass_idx),
|
||||||
if neighbor_pass_idx >= 0 {
|
if neighbor_pass_idx >= 0 {
|
||||||
Some(uniform_idx_as_vec2(neighbor_pass_idx as usize))
|
Some(uniform_idx_as_vec2(map_size_lg, neighbor_pass_idx as usize))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
},
|
},
|
||||||
@ -550,6 +566,7 @@ pub fn get_rivers<F: fmt::Debug + Float + Into<f64>, G: Float + Into<f64>>(
|
|||||||
/// TODO: See if allocating in advance is worthwhile.
|
/// TODO: See if allocating in advance is worthwhile.
|
||||||
#[allow(clippy::let_and_return)] // TODO: Pending review in #587
|
#[allow(clippy::let_and_return)] // TODO: Pending review in #587
|
||||||
fn get_max_slope(
|
fn get_max_slope(
|
||||||
|
map_size_lg: MapSizeLg,
|
||||||
h: &[Alt],
|
h: &[Alt],
|
||||||
rock_strength_nz: &(impl NoiseFn<Point3<f64>> + Sync),
|
rock_strength_nz: &(impl NoiseFn<Point3<f64>> + Sync),
|
||||||
height_scale: impl Fn(usize) -> Alt + Sync,
|
height_scale: impl Fn(usize) -> Alt + Sync,
|
||||||
@ -560,7 +577,7 @@ fn get_max_slope(
|
|||||||
h.par_iter()
|
h.par_iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(posi, &z)| {
|
.map(|(posi, &z)| {
|
||||||
let wposf = uniform_idx_as_vec2(posi).map(|e| e as f64)
|
let wposf = uniform_idx_as_vec2(map_size_lg, posi).map(|e| e as f64)
|
||||||
* TerrainChunkSize::RECT_SIZE.map(|e| e as f64);
|
* TerrainChunkSize::RECT_SIZE.map(|e| e as f64);
|
||||||
let height_scale = height_scale(posi);
|
let height_scale = height_scale(posi);
|
||||||
let wposz = z as f64 / height_scale as f64;
|
let wposz = z as f64 / height_scale as f64;
|
||||||
@ -685,6 +702,8 @@ fn get_max_slope(
|
|||||||
#[allow(clippy::many_single_char_names)]
|
#[allow(clippy::many_single_char_names)]
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
fn erode(
|
fn erode(
|
||||||
|
// Underlying map dimensions.
|
||||||
|
map_size_lg: MapSizeLg,
|
||||||
// Height above sea level of topsoil
|
// Height above sea level of topsoil
|
||||||
h: &mut [Alt],
|
h: &mut [Alt],
|
||||||
// Height above sea level of bedrock
|
// Height above sea level of bedrock
|
||||||
@ -733,8 +752,8 @@ fn erode(
|
|||||||
// at the cost of less interesting erosion behavior (linear vs. nonlinear).
|
// at the cost of less interesting erosion behavior (linear vs. nonlinear).
|
||||||
let q_ = 1.5;
|
let q_ = 1.5;
|
||||||
let k_da = 2.5 * k_da_scale(q);
|
let k_da = 2.5 * k_da_scale(q);
|
||||||
let nx = WORLD_SIZE.x;
|
let nx = usize::from(map_size_lg.chunks().x);
|
||||||
let ny = WORLD_SIZE.y;
|
let ny = usize::from(map_size_lg.chunks().y);
|
||||||
let dx = TerrainChunkSize::RECT_SIZE.x as f64;
|
let dx = TerrainChunkSize::RECT_SIZE.x as f64;
|
||||||
let dy = TerrainChunkSize::RECT_SIZE.y as f64;
|
let dy = TerrainChunkSize::RECT_SIZE.y as f64;
|
||||||
|
|
||||||
@ -795,11 +814,17 @@ fn erode(
|
|||||||
let g_fs_mult_sed = 1.0;
|
let g_fs_mult_sed = 1.0;
|
||||||
let ((dh, newh, maxh, mrec, mstack, mwrec, area), (mut max_slopes, h_t)) = rayon::join(
|
let ((dh, newh, maxh, mrec, mstack, mwrec, area), (mut max_slopes, h_t)) = rayon::join(
|
||||||
|| {
|
|| {
|
||||||
let mut dh = downhill(|posi| h[posi], |posi| is_ocean(posi) && h[posi] <= 0.0);
|
let mut dh = downhill(
|
||||||
|
map_size_lg,
|
||||||
|
|posi| h[posi],
|
||||||
|
|posi| is_ocean(posi) && h[posi] <= 0.0,
|
||||||
|
);
|
||||||
debug!("Computed downhill...");
|
debug!("Computed downhill...");
|
||||||
let (boundary_len, _indirection, newh, maxh) = get_lakes(|posi| h[posi], &mut dh);
|
let (boundary_len, _indirection, newh, maxh) =
|
||||||
|
get_lakes(map_size_lg, |posi| h[posi], &mut dh);
|
||||||
debug!("Got lakes...");
|
debug!("Got lakes...");
|
||||||
let (mrec, mstack, mwrec) = get_multi_rec(
|
let (mrec, mstack, mwrec) = get_multi_rec(
|
||||||
|
map_size_lg,
|
||||||
|posi| h[posi],
|
|posi| h[posi],
|
||||||
&dh,
|
&dh,
|
||||||
&newh,
|
&newh,
|
||||||
@ -813,16 +838,17 @@ fn erode(
|
|||||||
debug!("Got multiple receivers...");
|
debug!("Got multiple receivers...");
|
||||||
// TODO: Figure out how to switch between single-receiver and multi-receiver
|
// TODO: Figure out how to switch between single-receiver and multi-receiver
|
||||||
// drainage, as the former is much less computationally costly.
|
// drainage, as the former is much less computationally costly.
|
||||||
// let area = get_drainage(&newh, &dh, boundary_len);
|
// let area = get_drainage(map_size_lg, &newh, &dh, boundary_len);
|
||||||
let area = get_multi_drainage(&mstack, &mrec, &*mwrec, boundary_len);
|
let area = get_multi_drainage(map_size_lg, &mstack, &mrec, &*mwrec, boundary_len);
|
||||||
debug!("Got flux...");
|
debug!("Got flux...");
|
||||||
(dh, newh, maxh, mrec, mstack, mwrec, area)
|
(dh, newh, maxh, mrec, mstack, mwrec, area)
|
||||||
},
|
},
|
||||||
|| {
|
|| {
|
||||||
rayon::join(
|
rayon::join(
|
||||||
|| {
|
|| {
|
||||||
let max_slope =
|
let max_slope = get_max_slope(map_size_lg, h, rock_strength_nz, |posi| {
|
||||||
get_max_slope(h, rock_strength_nz, |posi| height_scale(n_f(posi)));
|
height_scale(n_f(posi))
|
||||||
|
});
|
||||||
debug!("Got max slopes...");
|
debug!("Got max slopes...");
|
||||||
max_slope
|
max_slope
|
||||||
},
|
},
|
||||||
@ -870,9 +896,10 @@ fn erode(
|
|||||||
let m = m_f(posi) as f64;
|
let m = m_f(posi) as f64;
|
||||||
|
|
||||||
let mwrec_i = &mwrec[posi];
|
let mwrec_i = &mwrec[posi];
|
||||||
mrec_downhill(&mrec, posi).for_each(|(kk, posj)| {
|
mrec_downhill(map_size_lg, &mrec, posi).for_each(|(kk, posj)| {
|
||||||
let dxy = (uniform_idx_as_vec2(posi) - uniform_idx_as_vec2(posj))
|
let dxy = (uniform_idx_as_vec2(map_size_lg, posi)
|
||||||
.map(|e| e as f64);
|
- uniform_idx_as_vec2(map_size_lg, posj))
|
||||||
|
.map(|e| e as f64);
|
||||||
let neighbor_distance = (neighbor_coef * dxy).magnitude();
|
let neighbor_distance = (neighbor_coef * dxy).magnitude();
|
||||||
let knew = (k
|
let knew = (k
|
||||||
* (p as f64
|
* (p as f64
|
||||||
@ -919,7 +946,7 @@ fn erode(
|
|||||||
let k_da = k_da * kd_factor;
|
let k_da = k_da * kd_factor;
|
||||||
|
|
||||||
let mwrec_i = &mwrec[posi];
|
let mwrec_i = &mwrec[posi];
|
||||||
mrec_downhill(&mrec, posi).for_each(|(kk, posj)| {
|
mrec_downhill(map_size_lg, &mrec, posi).for_each(|(kk, posj)| {
|
||||||
let mwrec_kk = mwrec_i[kk] as f64;
|
let mwrec_kk = mwrec_i[kk] as f64;
|
||||||
|
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
@ -965,8 +992,9 @@ fn erode(
|
|||||||
let k = (mwrec_kk * (uplift_i + max_uplift as f64 * g_i / p as f64))
|
let k = (mwrec_kk * (uplift_i + max_uplift as f64 * g_i / p as f64))
|
||||||
/ (1.0 + k_da * (mwrec_kk * chunk_neutral_area).powf(q))
|
/ (1.0 + k_da * (mwrec_kk * chunk_neutral_area).powf(q))
|
||||||
/ max_slope.powf(q_);
|
/ max_slope.powf(q_);
|
||||||
let dxy = (uniform_idx_as_vec2(posi) - uniform_idx_as_vec2(posj))
|
let dxy = (uniform_idx_as_vec2(map_size_lg, posi)
|
||||||
.map(|e| e as f64);
|
- uniform_idx_as_vec2(map_size_lg, posj))
|
||||||
|
.map(|e| e as f64);
|
||||||
let neighbor_distance = (neighbor_coef * dxy).magnitude();
|
let neighbor_distance = (neighbor_coef * dxy).magnitude();
|
||||||
|
|
||||||
let knew = (k
|
let knew = (k
|
||||||
@ -984,11 +1012,10 @@ fn erode(
|
|||||||
|
|
||||||
debug!("Computed stream power factors...");
|
debug!("Computed stream power factors...");
|
||||||
|
|
||||||
let mut lake_water_volume =
|
let mut lake_water_volume = vec![0.0 as Compute; map_size_lg.chunks_len()].into_boxed_slice();
|
||||||
vec![0.0 as Compute; WORLD_SIZE.x * WORLD_SIZE.y].into_boxed_slice();
|
let mut elev = vec![0.0 as Compute; map_size_lg.chunks_len()].into_boxed_slice();
|
||||||
let mut elev = vec![0.0 as Compute; WORLD_SIZE.x * WORLD_SIZE.y].into_boxed_slice();
|
let mut h_p = vec![0.0 as Compute; map_size_lg.chunks_len()].into_boxed_slice();
|
||||||
let mut h_p = vec![0.0 as Compute; WORLD_SIZE.x * WORLD_SIZE.y].into_boxed_slice();
|
let mut deltah = vec![0.0 as Compute; map_size_lg.chunks_len()].into_boxed_slice();
|
||||||
let mut deltah = vec![0.0 as Compute; WORLD_SIZE.x * WORLD_SIZE.y].into_boxed_slice();
|
|
||||||
|
|
||||||
// calculate the elevation / SPL, including sediment flux
|
// calculate the elevation / SPL, including sediment flux
|
||||||
let tol1 = 1.0e-4 as Compute * (maxh as Compute + 1.0);
|
let tol1 = 1.0e-4 as Compute * (maxh as Compute + 1.0);
|
||||||
@ -1022,8 +1049,8 @@ fn erode(
|
|||||||
|
|
||||||
// Gauss-Seidel iteration
|
// Gauss-Seidel iteration
|
||||||
|
|
||||||
let mut lake_silt = vec![0.0 as Compute; WORLD_SIZE.x * WORLD_SIZE.y].into_boxed_slice();
|
let mut lake_silt = vec![0.0 as Compute; map_size_lg.chunks_len()].into_boxed_slice();
|
||||||
let mut lake_sill = vec![-1isize; WORLD_SIZE.x * WORLD_SIZE.y].into_boxed_slice();
|
let mut lake_sill = vec![-1isize; map_size_lg.chunks_len()].into_boxed_slice();
|
||||||
|
|
||||||
let mut n_gs_stream_power_law = 0;
|
let mut n_gs_stream_power_law = 0;
|
||||||
// NOTE: Increasing this can theoretically sometimes be necessary for
|
// NOTE: Increasing this can theoretically sometimes be necessary for
|
||||||
@ -1120,7 +1147,7 @@ fn erode(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
let mwrec_i = &mwrec[posi];
|
let mwrec_i = &mwrec[posi];
|
||||||
mrec_downhill(&mrec, posi).for_each(|(k, posj)| {
|
mrec_downhill(map_size_lg, &mrec, posi).for_each(|(k, posj)| {
|
||||||
let stack_posj = mstack_inv[posj];
|
let stack_posj = mstack_inv[posj];
|
||||||
deltah[stack_posj] += deltah_i * mwrec_i[k];
|
deltah[stack_posj] += deltah_i * mwrec_i[k];
|
||||||
});
|
});
|
||||||
@ -1269,7 +1296,7 @@ fn erode(
|
|||||||
if (n - 1.0).abs() <= 1.0e-3 && (q_ - 1.0).abs() <= 1.0e-3 {
|
if (n - 1.0).abs() <= 1.0e-3 && (q_ - 1.0).abs() <= 1.0e-3 {
|
||||||
let mut f = h0;
|
let mut f = h0;
|
||||||
let mut df = 1.0;
|
let mut df = 1.0;
|
||||||
mrec_downhill(&mrec, posi).for_each(|(kk, posj)| {
|
mrec_downhill(map_size_lg, &mrec, posi).for_each(|(kk, posj)| {
|
||||||
let posj_stack = mstack_inv[posj];
|
let posj_stack = mstack_inv[posj];
|
||||||
let h_j = h_stack[posj_stack] as f64;
|
let h_j = h_stack[posj_stack] as f64;
|
||||||
// This can happen in cases where receiver kk is neither uphill of
|
// This can happen in cases where receiver kk is neither uphill of
|
||||||
@ -1295,7 +1322,7 @@ fn erode(
|
|||||||
let mut errp = 2.0 * tolp;
|
let mut errp = 2.0 * tolp;
|
||||||
let mut rec_heights = [0.0; 8];
|
let mut rec_heights = [0.0; 8];
|
||||||
let mut mask = [MaskType::new(false); 8];
|
let mut mask = [MaskType::new(false); 8];
|
||||||
mrec_downhill(&mrec, posi).for_each(|(kk, posj)| {
|
mrec_downhill(map_size_lg, &mrec, posi).for_each(|(kk, posj)| {
|
||||||
let posj_stack = mstack_inv[posj];
|
let posj_stack = mstack_inv[posj];
|
||||||
let h_j = h_stack[posj_stack];
|
let h_j = h_stack[posj_stack];
|
||||||
// NOTE: Fastscape does h_t[posi] + uplift(posi) as f64 >= h_t[posj]
|
// NOTE: Fastscape does h_t[posi] + uplift(posi) as f64 >= h_t[posj]
|
||||||
@ -1383,8 +1410,9 @@ fn erode(
|
|||||||
// of wh_j.
|
// of wh_j.
|
||||||
new_h_i = wh_j;
|
new_h_i = wh_j;
|
||||||
} else if compute_stats && new_h_i > 0.0 {
|
} else if compute_stats && new_h_i > 0.0 {
|
||||||
let dxy = (uniform_idx_as_vec2(posi) - uniform_idx_as_vec2(posj))
|
let dxy = (uniform_idx_as_vec2(map_size_lg, posi)
|
||||||
.map(|e| e as f64);
|
- uniform_idx_as_vec2(map_size_lg, posj))
|
||||||
|
.map(|e| e as f64);
|
||||||
let neighbor_distance = (neighbor_coef * dxy).magnitude();
|
let neighbor_distance = (neighbor_coef * dxy).magnitude();
|
||||||
let dz = (new_h_i - wh_j).max(0.0);
|
let dz = (new_h_i - wh_j).max(0.0);
|
||||||
let mag_slope = dz / neighbor_distance;
|
let mag_slope = dz / neighbor_distance;
|
||||||
@ -1479,7 +1507,9 @@ fn erode(
|
|||||||
} else {
|
} else {
|
||||||
let posj = posj as usize;
|
let posj = posj as usize;
|
||||||
let h_j = h[posj];
|
let h_j = h[posj];
|
||||||
let dxy = (uniform_idx_as_vec2(posi) - uniform_idx_as_vec2(posj)).map(|e| e as f64);
|
let dxy = (uniform_idx_as_vec2(map_size_lg, posi)
|
||||||
|
- uniform_idx_as_vec2(map_size_lg, posj))
|
||||||
|
.map(|e| e as f64);
|
||||||
let neighbor_distance_squared = (neighbor_coef * dxy).magnitude_squared();
|
let neighbor_distance_squared = (neighbor_coef * dxy).magnitude_squared();
|
||||||
let dh = (h_i - h_j) as f64;
|
let dh = (h_i - h_j) as f64;
|
||||||
// H_i_fact = (h_i - h_j) / (||p_i - p_j||^2 + (h_i - h_j)^2)
|
// H_i_fact = (h_i - h_j) / (||p_i - p_j||^2 + (h_i - h_j)^2)
|
||||||
@ -1608,7 +1638,9 @@ fn erode(
|
|||||||
// redistribute uplift to other neighbors.
|
// redistribute uplift to other neighbors.
|
||||||
let (posk, h_k) = (posj, h_j);
|
let (posk, h_k) = (posj, h_j);
|
||||||
let (posk, h_k) = if h_k < h_j { (posk, h_k) } else { (posj, 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 dxy = (uniform_idx_as_vec2(map_size_lg, posi)
|
||||||
|
- uniform_idx_as_vec2(map_size_lg, posk))
|
||||||
|
.map(|e| e as f64);
|
||||||
let neighbor_distance = (neighbor_coef * dxy).magnitude();
|
let neighbor_distance = (neighbor_coef * dxy).magnitude();
|
||||||
let dz = (new_h_i - h_k).max(0.0);
|
let dz = (new_h_i - h_k).max(0.0);
|
||||||
let mag_slope = dz / neighbor_distance;
|
let mag_slope = dz / neighbor_distance;
|
||||||
@ -1706,6 +1738,7 @@ fn erode(
|
|||||||
///
|
///
|
||||||
/// See https://github.com/mewo2/terrain/blob/master/terrain.js
|
/// See https://github.com/mewo2/terrain/blob/master/terrain.js
|
||||||
pub fn fill_sinks<F: Float + Send + Sync>(
|
pub fn fill_sinks<F: Float + Send + Sync>(
|
||||||
|
map_size_lg: MapSizeLg,
|
||||||
h: impl Fn(usize) -> F + Sync,
|
h: impl Fn(usize) -> F + Sync,
|
||||||
is_ocean: impl Fn(usize) -> bool + Sync,
|
is_ocean: impl Fn(usize) -> bool + Sync,
|
||||||
) -> Box<[F]> {
|
) -> Box<[F]> {
|
||||||
@ -1713,7 +1746,7 @@ pub fn fill_sinks<F: Float + Send + Sync>(
|
|||||||
// but doesn't change altitudes.
|
// but doesn't change altitudes.
|
||||||
let epsilon = F::zero();
|
let epsilon = F::zero();
|
||||||
let infinity = F::infinity();
|
let infinity = F::infinity();
|
||||||
let range = 0..WORLD_SIZE.x * WORLD_SIZE.y;
|
let range = 0..map_size_lg.chunks_len();
|
||||||
let oldh = range
|
let oldh = range
|
||||||
.into_par_iter()
|
.into_par_iter()
|
||||||
.map(|posi| h(posi))
|
.map(|posi| h(posi))
|
||||||
@ -1742,7 +1775,7 @@ pub fn fill_sinks<F: Float + Send + Sync>(
|
|||||||
if nh == oh {
|
if nh == oh {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for nposi in neighbors(posi) {
|
for nposi in neighbors(map_size_lg, posi) {
|
||||||
let onbh = newh[nposi];
|
let onbh = newh[nposi];
|
||||||
let nbh = onbh + epsilon;
|
let nbh = onbh + epsilon;
|
||||||
// If there is even one path downhill from this node's original height, fix
|
// If there is even one path downhill from this node's original height, fix
|
||||||
@ -1793,6 +1826,7 @@ pub fn fill_sinks<F: Float + Send + Sync>(
|
|||||||
/// indirection vector.
|
/// indirection vector.
|
||||||
#[allow(clippy::filter_next)] // TODO: Pending review in #587
|
#[allow(clippy::filter_next)] // TODO: Pending review in #587
|
||||||
pub fn get_lakes<F: Float>(
|
pub fn get_lakes<F: Float>(
|
||||||
|
map_size_lg: MapSizeLg,
|
||||||
h: impl Fn(usize) -> F,
|
h: impl Fn(usize) -> F,
|
||||||
downhill: &mut [isize],
|
downhill: &mut [isize],
|
||||||
) -> (usize, Box<[i32]>, Box<[u32]>, F) {
|
) -> (usize, Box<[i32]>, Box<[u32]>, F) {
|
||||||
@ -1817,7 +1851,7 @@ pub fn get_lakes<F: Float>(
|
|||||||
// node, so we should access that entry and increment it, then set our own
|
// node, so we should access that entry and increment it, then set our own
|
||||||
// entry to it.
|
// entry to it.
|
||||||
let mut boundary = Vec::with_capacity(downhill.len());
|
let mut boundary = Vec::with_capacity(downhill.len());
|
||||||
let mut indirection = vec![/*-1i32*/0i32; WORLD_SIZE.x * WORLD_SIZE.y].into_boxed_slice();
|
let mut indirection = vec![/*-1i32*/0i32; map_size_lg.chunks_len()].into_boxed_slice();
|
||||||
|
|
||||||
let mut newh = Vec::with_capacity(downhill.len());
|
let mut newh = Vec::with_capacity(downhill.len());
|
||||||
|
|
||||||
@ -1855,7 +1889,7 @@ pub fn get_lakes<F: Float>(
|
|||||||
let mut cur = start;
|
let mut cur = start;
|
||||||
while cur < newh.len() {
|
while cur < newh.len() {
|
||||||
let node = newh[cur as usize];
|
let node = newh[cur as usize];
|
||||||
uphill(downhill, node as usize).for_each(|child| {
|
uphill(map_size_lg, downhill, node as usize).for_each(|child| {
|
||||||
// lake_idx is the index of our lake root.
|
// lake_idx is the index of our lake root.
|
||||||
indirection[child] = chunk_idx as i32;
|
indirection[child] = chunk_idx as i32;
|
||||||
indirection_[child] = indirection_idx;
|
indirection_[child] = indirection_idx;
|
||||||
@ -1896,7 +1930,7 @@ pub fn get_lakes<F: Float>(
|
|||||||
// our own lake's entry list. If the maximum of the heights we get out
|
// our own lake's entry list. If the maximum of the heights we get out
|
||||||
// from this process is greater than the maximum of this chunk and its
|
// from this process is greater than the maximum of this chunk and its
|
||||||
// neighbor chunk, we switch to this new edge.
|
// neighbor chunk, we switch to this new edge.
|
||||||
neighbors(chunk_idx).for_each(|neighbor_idx| {
|
neighbors(map_size_lg, chunk_idx).for_each(|neighbor_idx| {
|
||||||
let neighbor_height = h(neighbor_idx);
|
let neighbor_height = h(neighbor_idx);
|
||||||
let neighbor_lake_idx_ = indirection_[neighbor_idx];
|
let neighbor_lake_idx_ = indirection_[neighbor_idx];
|
||||||
let neighbor_lake_idx = neighbor_lake_idx_ as usize;
|
let neighbor_lake_idx = neighbor_lake_idx_ as usize;
|
||||||
@ -1962,24 +1996,33 @@ pub fn get_lakes<F: Float>(
|
|||||||
"For edge {:?} between lakes {:?}, couldn't find partner for pass \
|
"For edge {:?} between lakes {:?}, couldn't find partner for pass \
|
||||||
{:?}. Should never happen; maybe forgot to set both edges?",
|
{:?}. Should never happen; maybe forgot to set both edges?",
|
||||||
(
|
(
|
||||||
(chunk_idx, uniform_idx_as_vec2(chunk_idx as usize)),
|
(
|
||||||
(neighbor_idx, uniform_idx_as_vec2(neighbor_idx as usize))
|
chunk_idx,
|
||||||
|
uniform_idx_as_vec2(map_size_lg, chunk_idx as usize)
|
||||||
|
),
|
||||||
|
(
|
||||||
|
neighbor_idx,
|
||||||
|
uniform_idx_as_vec2(map_size_lg, neighbor_idx as usize)
|
||||||
|
)
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
(
|
(
|
||||||
lake_chunk_idx,
|
lake_chunk_idx,
|
||||||
uniform_idx_as_vec2(lake_chunk_idx as usize),
|
uniform_idx_as_vec2(map_size_lg, lake_chunk_idx as usize),
|
||||||
lake_idx_
|
lake_idx_
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
neighbor_lake_chunk_idx,
|
neighbor_lake_chunk_idx,
|
||||||
uniform_idx_as_vec2(neighbor_lake_chunk_idx as usize),
|
uniform_idx_as_vec2(
|
||||||
|
map_size_lg,
|
||||||
|
neighbor_lake_chunk_idx as usize
|
||||||
|
),
|
||||||
neighbor_lake_idx_
|
neighbor_lake_idx_
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
(pass.0, uniform_idx_as_vec2(pass.0 as usize)),
|
(pass.0, uniform_idx_as_vec2(map_size_lg, pass.0 as usize)),
|
||||||
(pass.1, uniform_idx_as_vec2(pass.1 as usize))
|
(pass.1, uniform_idx_as_vec2(map_size_lg, pass.1 as usize))
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -2068,7 +2111,7 @@ pub fn get_lakes<F: Float>(
|
|||||||
downhill[lake_chunk_idx] = neighbor_idx as isize;
|
downhill[lake_chunk_idx] = neighbor_idx as isize;
|
||||||
// Also set the indirection of the lake bottom to the negation of the
|
// Also set the indirection of the lake bottom to the negation of the
|
||||||
// lake side of the chosen pass (chunk_idx).
|
// lake side of the chosen pass (chunk_idx).
|
||||||
// NOTE: This can't overflow i32 because WORLD_SIZE.x * WORLD_SIZE.y should fit
|
// NOTE: This can't overflow i32 because map_size_lg.chunks_len() should fit
|
||||||
// in an i32.
|
// in an i32.
|
||||||
indirection[lake_chunk_idx] = -(chunk_idx as i32);
|
indirection[lake_chunk_idx] = -(chunk_idx as i32);
|
||||||
// Add this edge to the sorted list.
|
// Add this edge to the sorted list.
|
||||||
@ -2114,7 +2157,7 @@ pub fn get_lakes<F: Float>(
|
|||||||
InQueue,
|
InQueue,
|
||||||
WithRcv,
|
WithRcv,
|
||||||
}
|
}
|
||||||
let mut tag = vec![Tag::UnParsed; WORLD_SIZE.x * WORLD_SIZE.y];
|
let mut tag = vec![Tag::UnParsed; map_size_lg.chunks_len()];
|
||||||
// TODO: Combine with adding to vector.
|
// TODO: Combine with adding to vector.
|
||||||
let mut filling_queue = Vec::with_capacity(downhill.len());
|
let mut filling_queue = Vec::with_capacity(downhill.len());
|
||||||
|
|
||||||
@ -2182,17 +2225,17 @@ pub fn get_lakes<F: Float>(
|
|||||||
tag[neighbor_pass_idx] = Tag::WithRcv;
|
tag[neighbor_pass_idx] = Tag::WithRcv;
|
||||||
tag[pass_idx] = Tag::InQueue;
|
tag[pass_idx] = Tag::InQueue;
|
||||||
|
|
||||||
let outflow_coords = uniform_idx_as_vec2(neighbor_pass_idx);
|
let outflow_coords = uniform_idx_as_vec2(map_size_lg, neighbor_pass_idx);
|
||||||
let elev = h(neighbor_pass_idx).max(h(pass_idx));
|
let elev = h(neighbor_pass_idx).max(h(pass_idx));
|
||||||
|
|
||||||
while let Some(node) = filling_queue.pop() {
|
while let Some(node) = filling_queue.pop() {
|
||||||
let coords = uniform_idx_as_vec2(node);
|
let coords = uniform_idx_as_vec2(map_size_lg, node);
|
||||||
|
|
||||||
let mut rcv = -1;
|
let mut rcv = -1;
|
||||||
let mut rcv_cost = -f64::INFINITY; /*f64::EPSILON;*/
|
let mut rcv_cost = -f64::INFINITY; /*f64::EPSILON;*/
|
||||||
let outflow_distance = (outflow_coords - coords).map(|e| e as f64);
|
let outflow_distance = (outflow_coords - coords).map(|e| e as f64);
|
||||||
|
|
||||||
neighbors(node).for_each(|ineighbor| {
|
neighbors(map_size_lg, node).for_each(|ineighbor| {
|
||||||
if indirection[ineighbor] != chunk_idx as i32
|
if indirection[ineighbor] != chunk_idx as i32
|
||||||
&& ineighbor != chunk_idx
|
&& ineighbor != chunk_idx
|
||||||
&& ineighbor != neighbor_pass_idx
|
&& ineighbor != neighbor_pass_idx
|
||||||
@ -2200,7 +2243,8 @@ pub fn get_lakes<F: Float>(
|
|||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let dxy = (uniform_idx_as_vec2(ineighbor) - coords).map(|e| e as f64);
|
let dxy = (uniform_idx_as_vec2(map_size_lg, ineighbor) - coords)
|
||||||
|
.map(|e| e as f64);
|
||||||
let neighbor_distance = /*neighbor_coef * */dxy;
|
let neighbor_distance = /*neighbor_coef * */dxy;
|
||||||
let tag = &mut tag[ineighbor];
|
let tag = &mut tag[ineighbor];
|
||||||
match *tag {
|
match *tag {
|
||||||
@ -2242,7 +2286,7 @@ pub fn get_lakes<F: Float>(
|
|||||||
let mut cur = start;
|
let mut cur = start;
|
||||||
let mut node = first_idx;
|
let mut node = first_idx;
|
||||||
loop {
|
loop {
|
||||||
uphill(downhill, node as usize).for_each(|child| {
|
uphill(map_size_lg, downhill, node as usize).for_each(|child| {
|
||||||
// lake_idx is the index of our lake root.
|
// lake_idx is the index of our lake root.
|
||||||
// Check to make sure child (flowing into us) is in the same lake.
|
// Check to make sure child (flowing into us) is in the same lake.
|
||||||
if indirection[child] == chunk_idx as i32 || child == chunk_idx {
|
if indirection[child] == chunk_idx as i32 || child == chunk_idx {
|
||||||
@ -2263,10 +2307,11 @@ pub fn get_lakes<F: Float>(
|
|||||||
|
|
||||||
/// Iterate through set neighbors of multi-receiver flow.
|
/// Iterate through set neighbors of multi-receiver flow.
|
||||||
pub fn mrec_downhill<'a>(
|
pub fn mrec_downhill<'a>(
|
||||||
|
map_size_lg: MapSizeLg,
|
||||||
mrec: &'a [u8],
|
mrec: &'a [u8],
|
||||||
posi: usize,
|
posi: usize,
|
||||||
) -> impl Clone + Iterator<Item = (usize, usize)> + 'a {
|
) -> impl Clone + Iterator<Item = (usize, usize)> + 'a {
|
||||||
let pos = uniform_idx_as_vec2(posi);
|
let pos = uniform_idx_as_vec2(map_size_lg, posi);
|
||||||
let mrec_i = mrec[posi];
|
let mrec_i = mrec[posi];
|
||||||
NEIGHBOR_DELTA
|
NEIGHBOR_DELTA
|
||||||
.iter()
|
.iter()
|
||||||
@ -2275,13 +2320,14 @@ pub fn mrec_downhill<'a>(
|
|||||||
.map(move |(k, &(x, y))| {
|
.map(move |(k, &(x, y))| {
|
||||||
(
|
(
|
||||||
k,
|
k,
|
||||||
vec2_as_uniform_idx(Vec2::new(pos.x + x as i32, pos.y + y as i32)),
|
vec2_as_uniform_idx(map_size_lg, Vec2::new(pos.x + x as i32, pos.y + y as i32)),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Algorithm for computing multi-receiver flow.
|
/// Algorithm for computing multi-receiver flow.
|
||||||
///
|
///
|
||||||
|
/// * `map_size_lg`: Size of the underlying map.
|
||||||
/// * `h`: altitude
|
/// * `h`: altitude
|
||||||
/// * `downhill`: single receiver
|
/// * `downhill`: single receiver
|
||||||
/// * `newh`: single receiver stack
|
/// * `newh`: single receiver stack
|
||||||
@ -2299,6 +2345,7 @@ pub fn mrec_downhill<'a>(
|
|||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
#[allow(clippy::type_complexity)] // TODO: Pending review in #587
|
#[allow(clippy::type_complexity)] // TODO: Pending review in #587
|
||||||
pub fn get_multi_rec<F: fmt::Debug + Float + Sync + Into<Compute>>(
|
pub fn get_multi_rec<F: fmt::Debug + Float + Sync + Into<Compute>>(
|
||||||
|
map_size_lg: MapSizeLg,
|
||||||
h: impl Fn(usize) -> F + Sync,
|
h: impl Fn(usize) -> F + Sync,
|
||||||
downhill: &[isize],
|
downhill: &[isize],
|
||||||
newh: &[u32],
|
newh: &[u32],
|
||||||
@ -2379,18 +2426,15 @@ pub fn get_multi_rec<F: fmt::Debug + Float + Sync + Into<Compute>>(
|
|||||||
let mut mrec_ij = 0u8;
|
let mut mrec_ij = 0u8;
|
||||||
let mut ndon_ij = 0u8;
|
let mut ndon_ij = 0u8;
|
||||||
let neighbor_iter = |posi| {
|
let neighbor_iter = |posi| {
|
||||||
let pos = uniform_idx_as_vec2(posi);
|
let pos = uniform_idx_as_vec2(map_size_lg, posi);
|
||||||
NEIGHBOR_DELTA
|
NEIGHBOR_DELTA
|
||||||
.iter()
|
.iter()
|
||||||
.map(move |&(x, y)| Vec2::new(pos.x + x, pos.y + y))
|
.map(move |&(x, y)| Vec2::new(pos.x + x, pos.y + y))
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.filter(move |&(_, pos)| {
|
.filter(move |&(_, pos)| {
|
||||||
pos.x >= 0
|
pos.x >= 0 && pos.y >= 0 && pos.x < nx as i32 && pos.y < ny as i32
|
||||||
&& pos.y >= 0
|
|
||||||
&& pos.x < WORLD_SIZE.x as i32
|
|
||||||
&& pos.y < WORLD_SIZE.y as i32
|
|
||||||
})
|
})
|
||||||
.map(move |(k, pos)| (k, vec2_as_uniform_idx(pos)))
|
.map(move |(k, pos)| (k, vec2_as_uniform_idx(map_size_lg, pos)))
|
||||||
};
|
};
|
||||||
|
|
||||||
neighbor_iter(ij).for_each(|(k, ijk)| {
|
neighbor_iter(ij).for_each(|(k, ijk)| {
|
||||||
@ -2417,9 +2461,10 @@ pub fn get_multi_rec<F: fmt::Debug + Float + Sync + Into<Compute>>(
|
|||||||
let mut sumweight = czero;
|
let mut sumweight = czero;
|
||||||
let mut wrec = [czero; 8];
|
let mut wrec = [czero; 8];
|
||||||
let mut nrec = 0;
|
let mut nrec = 0;
|
||||||
mrec_downhill(&mrec, ij).for_each(|(k, ijk)| {
|
mrec_downhill(map_size_lg, &mrec, ij).for_each(|(k, ijk)| {
|
||||||
let lrec_ijk = ((uniform_idx_as_vec2(ijk) - uniform_idx_as_vec2(ij))
|
let lrec_ijk = ((uniform_idx_as_vec2(map_size_lg, ijk)
|
||||||
.map(|e| e as Compute)
|
- uniform_idx_as_vec2(map_size_lg, ij))
|
||||||
|
.map(|e| e as Compute)
|
||||||
* dxdy)
|
* dxdy)
|
||||||
.magnitude();
|
.magnitude();
|
||||||
let wrec_ijk = (wh[ij] - wh[ijk]).into() / lrec_ijk;
|
let wrec_ijk = (wh[ij] - wh[ijk]).into() / lrec_ijk;
|
||||||
@ -2464,7 +2509,7 @@ pub fn get_multi_rec<F: fmt::Debug + Float + Sync + Into<Compute>>(
|
|||||||
while let Some(ijn) = parse.pop() {
|
while let Some(ijn) = parse.pop() {
|
||||||
// we add the node to the stack
|
// we add the node to the stack
|
||||||
stack.push(ijn as u32);
|
stack.push(ijn as u32);
|
||||||
mrec_downhill(&mrec, ijn).for_each(|(_, ijr)| {
|
mrec_downhill(map_size_lg, &mrec, ijn).for_each(|(_, ijr)| {
|
||||||
let (_, ref mut vis_ijr) = don_vis[ijr];
|
let (_, ref mut vis_ijr) = don_vis[ijr];
|
||||||
if *vis_ijr >= 1 {
|
if *vis_ijr >= 1 {
|
||||||
*vis_ijr -= 1;
|
*vis_ijr -= 1;
|
||||||
@ -2492,6 +2537,7 @@ pub fn get_multi_rec<F: fmt::Debug + Float + Sync + Into<Compute>>(
|
|||||||
#[allow(clippy::many_single_char_names)]
|
#[allow(clippy::many_single_char_names)]
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn do_erosion(
|
pub fn do_erosion(
|
||||||
|
map_size_lg: MapSizeLg,
|
||||||
_max_uplift: f32,
|
_max_uplift: f32,
|
||||||
n_steps: usize,
|
n_steps: usize,
|
||||||
seed: &RandomField,
|
seed: &RandomField,
|
||||||
@ -2513,59 +2559,59 @@ pub fn do_erosion(
|
|||||||
k_da_scale: impl Fn(f64) -> f64,
|
k_da_scale: impl Fn(f64) -> f64,
|
||||||
) -> (Box<[Alt]>, Box<[Alt]> /* , Box<[Alt]> */) {
|
) -> (Box<[Alt]>, Box<[Alt]> /* , Box<[Alt]> */) {
|
||||||
debug!("Initializing erosion arrays...");
|
debug!("Initializing erosion arrays...");
|
||||||
let oldh_ = (0..WORLD_SIZE.x * WORLD_SIZE.y)
|
let oldh_ = (0..map_size_lg.chunks_len())
|
||||||
.into_par_iter()
|
.into_par_iter()
|
||||||
.map(|posi| oldh(posi) as Alt)
|
.map(|posi| oldh(posi) as Alt)
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.into_boxed_slice();
|
.into_boxed_slice();
|
||||||
// Topographic basement (The height of bedrock, not including sediment).
|
// Topographic basement (The height of bedrock, not including sediment).
|
||||||
let mut b = (0..WORLD_SIZE.x * WORLD_SIZE.y)
|
let mut b = (0..map_size_lg.chunks_len())
|
||||||
.into_par_iter()
|
.into_par_iter()
|
||||||
.map(|posi| oldb(posi) as Alt)
|
.map(|posi| oldb(posi) as Alt)
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.into_boxed_slice();
|
.into_boxed_slice();
|
||||||
// Stream power law slope exponent--link between channel slope and erosion rate.
|
// Stream power law slope exponent--link between channel slope and erosion rate.
|
||||||
let n = (0..WORLD_SIZE.x * WORLD_SIZE.y)
|
let n = (0..map_size_lg.chunks_len())
|
||||||
.into_par_iter()
|
.into_par_iter()
|
||||||
.map(|posi| n(posi))
|
.map(|posi| n(posi))
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.into_boxed_slice();
|
.into_boxed_slice();
|
||||||
// Stream power law concavity index (θ = m/n), turned into an exponent on
|
// Stream power law concavity index (θ = m/n), turned into an exponent on
|
||||||
// drainage (which is a proxy for discharge according to Hack's Law).
|
// drainage (which is a proxy for discharge according to Hack's Law).
|
||||||
let m = (0..WORLD_SIZE.x * WORLD_SIZE.y)
|
let m = (0..map_size_lg.chunks_len())
|
||||||
.into_par_iter()
|
.into_par_iter()
|
||||||
.map(|posi| theta(posi) * n[posi])
|
.map(|posi| theta(posi) * n[posi])
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.into_boxed_slice();
|
.into_boxed_slice();
|
||||||
// Stream power law erodability constant for fluvial erosion (bedrock)
|
// Stream power law erodability constant for fluvial erosion (bedrock)
|
||||||
let kf = (0..WORLD_SIZE.x * WORLD_SIZE.y)
|
let kf = (0..map_size_lg.chunks_len())
|
||||||
.into_par_iter()
|
.into_par_iter()
|
||||||
.map(|posi| kf(posi))
|
.map(|posi| kf(posi))
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.into_boxed_slice();
|
.into_boxed_slice();
|
||||||
// Stream power law erodability constant for hillslope diffusion (bedrock)
|
// Stream power law erodability constant for hillslope diffusion (bedrock)
|
||||||
let kd = (0..WORLD_SIZE.x * WORLD_SIZE.y)
|
let kd = (0..map_size_lg.chunks_len())
|
||||||
.into_par_iter()
|
.into_par_iter()
|
||||||
.map(|posi| kd(posi))
|
.map(|posi| kd(posi))
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.into_boxed_slice();
|
.into_boxed_slice();
|
||||||
// Deposition coefficient
|
// Deposition coefficient
|
||||||
let g = (0..WORLD_SIZE.x * WORLD_SIZE.y)
|
let g = (0..map_size_lg.chunks_len())
|
||||||
.into_par_iter()
|
.into_par_iter()
|
||||||
.map(|posi| g(posi))
|
.map(|posi| g(posi))
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.into_boxed_slice();
|
.into_boxed_slice();
|
||||||
let epsilon_0 = (0..WORLD_SIZE.x * WORLD_SIZE.y)
|
let epsilon_0 = (0..map_size_lg.chunks_len())
|
||||||
.into_par_iter()
|
.into_par_iter()
|
||||||
.map(|posi| epsilon_0(posi))
|
.map(|posi| epsilon_0(posi))
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.into_boxed_slice();
|
.into_boxed_slice();
|
||||||
let alpha = (0..WORLD_SIZE.x * WORLD_SIZE.y)
|
let alpha = (0..map_size_lg.chunks_len())
|
||||||
.into_par_iter()
|
.into_par_iter()
|
||||||
.map(|posi| alpha(posi))
|
.map(|posi| alpha(posi))
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.into_boxed_slice();
|
.into_boxed_slice();
|
||||||
let mut wh = vec![0.0; WORLD_SIZE.x * WORLD_SIZE.y].into_boxed_slice();
|
let mut wh = vec![0.0; map_size_lg.chunks_len()].into_boxed_slice();
|
||||||
// TODO: Don't do this, maybe?
|
// TODO: Don't do this, maybe?
|
||||||
// (To elaborate, maybe we should have varying uplift or compute it some other
|
// (To elaborate, maybe we should have varying uplift or compute it some other
|
||||||
// way).
|
// way).
|
||||||
@ -2613,6 +2659,7 @@ pub fn do_erosion(
|
|||||||
(0..n_steps).for_each(|i| {
|
(0..n_steps).for_each(|i| {
|
||||||
debug!("Erosion iteration #{:?}", i);
|
debug!("Erosion iteration #{:?}", i);
|
||||||
erode(
|
erode(
|
||||||
|
map_size_lg,
|
||||||
&mut h,
|
&mut h,
|
||||||
&mut b,
|
&mut b,
|
||||||
&mut wh,
|
&mut wh,
|
||||||
|
@ -1,712 +1,264 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
column::{quadratic_nearest_point, river_spline_coeffs, ColumnSample},
|
column::ColumnSample,
|
||||||
sim::{
|
sim::{RiverKind, WorldSim},
|
||||||
neighbors, uniform_idx_as_vec2, vec2_as_uniform_idx, Alt, RiverKind, WorldSim,
|
|
||||||
NEIGHBOR_DELTA, WORLD_SIZE,
|
|
||||||
},
|
|
||||||
CONFIG,
|
CONFIG,
|
||||||
};
|
};
|
||||||
use common::{terrain::TerrainChunkSize, vol::RectVolSize};
|
use common::{
|
||||||
use std::{f32, f64, iter};
|
terrain::{
|
||||||
|
map::{Connection, ConnectionKind, MapConfig, MapSample},
|
||||||
|
vec2_as_uniform_idx, TerrainChunkSize, NEIGHBOR_DELTA,
|
||||||
|
},
|
||||||
|
vol::RectVolSize,
|
||||||
|
};
|
||||||
|
use std::{f32, f64};
|
||||||
use vek::*;
|
use vek::*;
|
||||||
|
|
||||||
pub struct MapConfig<'a> {
|
/// A sample function that grabs the connections at a chunk.
|
||||||
/// Dimensions of the window being written to. Defaults to WORLD_SIZE.
|
///
|
||||||
pub dimensions: Vec2<usize>,
|
/// Currently this just supports rivers, but ideally it can be extended past
|
||||||
/// x, y, and z of top left of map (defaults to (0.0, 0.0,
|
/// that.
|
||||||
/// CONFIG.sea_level)).
|
///
|
||||||
pub focus: Vec3<f64>,
|
/// A sample function that grabs surface altitude at a column.
|
||||||
/// Altitude is divided by gain and clamped to [0, 1]; thus, decreasing gain
|
/// (correctly reflecting settings like is_basement and is_water).
|
||||||
/// makes smaller differences in altitude appear larger.
|
///
|
||||||
///
|
/// The altitude produced by this function at a column corresponding to a
|
||||||
/// Defaults to CONFIG.mountain_scale.
|
/// particular chunk should be identical to the altitude produced by
|
||||||
pub gain: f32,
|
/// sample_pos at that chunk.
|
||||||
/// lgain is used for shading purposes and refers to how much impact a
|
///
|
||||||
/// change in the z direction has on the perceived slope relative to the
|
/// You should generally pass a closure over this function into generate
|
||||||
/// same change in x and y.
|
/// when constructing a map for the first time.
|
||||||
///
|
/// However, if repeated construction is needed, or alternate base colors
|
||||||
/// Defaults to TerrainChunkSize::RECT_SIZE.x.
|
/// are to be used for some reason, one should pass a custom function to
|
||||||
pub lgain: f64,
|
/// generate instead (e.g. one that just looks up the height in a cached
|
||||||
/// Scale is like gain, but for x and y rather than z.
|
/// array).
|
||||||
///
|
pub fn sample_wpos(config: &MapConfig, sampler: &WorldSim, wpos: Vec2<i32>) -> f32 {
|
||||||
/// Defaults to WORLD_SIZE.x / dimensions.x (NOTE: fractional, not integer,
|
let MapConfig {
|
||||||
/// division!).
|
focus,
|
||||||
pub scale: f64,
|
gain,
|
||||||
/// Vector that indicates which direction light is coming from, if shading
|
|
||||||
/// is turned on.
|
|
||||||
///
|
|
||||||
/// 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"
|
|
||||||
///
|
|
||||||
/// Defaults to (-0.8, -1.0, 0.3).
|
|
||||||
pub light_direction: Vec3<f64>,
|
|
||||||
/// If Some, uses the provided horizon map.
|
|
||||||
///
|
|
||||||
/// Defaults to None.
|
|
||||||
pub horizons: Option<&'a [(Vec<Alt>, Vec<Alt>); 2]>,
|
|
||||||
/// If Some, uses the provided column samples to determine surface color.
|
|
||||||
///
|
|
||||||
/// Defaults to None.
|
|
||||||
pub samples: Option<&'a [Option<ColumnSample<'a>>]>,
|
|
||||||
/// If true, only the basement (bedrock) is used for altitude; otherwise,
|
|
||||||
/// the surface is used.
|
|
||||||
///
|
|
||||||
/// Defaults to false.
|
|
||||||
pub is_basement: bool,
|
|
||||||
/// If true, water is rendered; otherwise, the surface without water is
|
|
||||||
/// rendered, even if it is underwater.
|
|
||||||
///
|
|
||||||
/// Defaults to true.
|
|
||||||
pub is_water: bool,
|
|
||||||
/// If true, 3D lighting and shading are turned on. Otherwise, a plain
|
|
||||||
/// altitude map is used.
|
|
||||||
///
|
|
||||||
/// Defaults to true.
|
|
||||||
pub is_shaded: bool,
|
|
||||||
/// If true, the red component of the image is also used for temperature
|
|
||||||
/// (redder is hotter). Defaults to false.
|
|
||||||
pub is_temperature: bool,
|
|
||||||
/// If true, the blue component of the image is also used for humidity
|
|
||||||
/// (bluer is wetter).
|
|
||||||
///
|
|
||||||
/// Defaults to false.
|
|
||||||
pub is_humidity: bool,
|
|
||||||
/// Record debug information.
|
|
||||||
///
|
|
||||||
/// Defaults to false.
|
|
||||||
pub is_debug: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const QUADRANTS: usize = 4;
|
is_basement,
|
||||||
|
is_water,
|
||||||
|
..
|
||||||
|
} = *config;
|
||||||
|
|
||||||
pub struct MapDebug {
|
(sampler
|
||||||
pub quads: [[u32; QUADRANTS]; QUADRANTS],
|
.get_wpos(wpos)
|
||||||
pub rivers: u32,
|
.map(|s| {
|
||||||
pub lakes: u32,
|
if is_basement { s.basement } else { s.alt }.max(if is_water {
|
||||||
pub oceans: u32,
|
s.water_alt
|
||||||
}
|
} else {
|
||||||
|
-f32::INFINITY
|
||||||
impl<'a> Default for MapConfig<'a> {
|
|
||||||
fn default() -> Self {
|
|
||||||
let dimensions = WORLD_SIZE;
|
|
||||||
Self {
|
|
||||||
dimensions,
|
|
||||||
focus: Vec3::new(0.0, 0.0, CONFIG.sea_level as f64),
|
|
||||||
gain: CONFIG.mountain_scale,
|
|
||||||
lgain: TerrainChunkSize::RECT_SIZE.x as f64,
|
|
||||||
scale: WORLD_SIZE.x as f64 / dimensions.x as f64,
|
|
||||||
light_direction: Vec3::new(-1.2, -1.0, 0.8),
|
|
||||||
horizons: None,
|
|
||||||
samples: None,
|
|
||||||
|
|
||||||
is_basement: false,
|
|
||||||
is_water: true,
|
|
||||||
is_shaded: true,
|
|
||||||
is_temperature: false,
|
|
||||||
is_humidity: false,
|
|
||||||
is_debug: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Connection kind (per edge). Currently just supports rivers, but may be
|
|
||||||
/// extended to support paths or at least one other kind of connection.
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
|
||||||
pub enum ConnectionKind {
|
|
||||||
/// Connection forms a visible river.
|
|
||||||
River,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Map connection (per edge).
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
|
||||||
pub struct Connection {
|
|
||||||
/// The kind of connection this is (e.g. river or path).
|
|
||||||
pub kind: ConnectionKind,
|
|
||||||
/// Assumed to be the "b" part of a 2d quadratic function.
|
|
||||||
pub spline_derivative: Vec2<f32>,
|
|
||||||
/// Width of the connection.
|
|
||||||
pub width: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Per-chunk data the map needs to be able to sample in order to correctly
|
|
||||||
/// render.
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct MapSample {
|
|
||||||
/// the base RGB color for a particular map pixel using the current settings
|
|
||||||
/// (i.e. the color *without* lighting).
|
|
||||||
pub rgb: Rgb<u8>,
|
|
||||||
/// Surface altitude information
|
|
||||||
/// (correctly reflecting settings like is_basement and is_water)
|
|
||||||
pub alt: f64,
|
|
||||||
/// Downhill chunk (may not be meaningful on ocean tiles, or at least edge
|
|
||||||
/// tiles)
|
|
||||||
pub downhill_wpos: Vec2<i32>,
|
|
||||||
/// Connection information about any connections to/from this chunk (e.g.
|
|
||||||
/// rivers).
|
|
||||||
///
|
|
||||||
/// Connections at each index correspond to the same index in
|
|
||||||
/// NEIGHBOR_DELTA.
|
|
||||||
pub connections: Option<[Option<Connection>; 8]>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> MapConfig<'a> {
|
|
||||||
/// A sample function that grabs the connections at a chunk.
|
|
||||||
///
|
|
||||||
/// Currently this just supports rivers, but ideally it can be extended past
|
|
||||||
/// that.
|
|
||||||
///
|
|
||||||
/// A sample function that grabs surface altitude at a column.
|
|
||||||
/// (correctly reflecting settings like is_basement and is_water).
|
|
||||||
///
|
|
||||||
/// The altitude produced by this function at a column corresponding to a
|
|
||||||
/// particular chunk should be identical to the altitude produced by
|
|
||||||
/// sample_pos at that chunk.
|
|
||||||
///
|
|
||||||
/// You should generally pass a closure over this function into generate
|
|
||||||
/// when constructing a map for the first time.
|
|
||||||
/// However, if repeated construction is needed, or alternate base colors
|
|
||||||
/// are to be used for some reason, one should pass a custom function to
|
|
||||||
/// generate instead (e.g. one that just looks up the height in a cached
|
|
||||||
/// array).
|
|
||||||
pub fn sample_wpos(&self, sampler: &WorldSim, wpos: Vec2<i32>) -> f32 {
|
|
||||||
let MapConfig {
|
|
||||||
focus,
|
|
||||||
gain,
|
|
||||||
|
|
||||||
is_basement,
|
|
||||||
is_water,
|
|
||||||
..
|
|
||||||
} = *self;
|
|
||||||
|
|
||||||
(sampler
|
|
||||||
.get_wpos(wpos)
|
|
||||||
.map(|s| {
|
|
||||||
if is_basement { s.basement } else { s.alt }.max(if is_water {
|
|
||||||
s.water_alt
|
|
||||||
} else {
|
|
||||||
-f32::INFINITY
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
.unwrap_or(CONFIG.sea_level)
|
})
|
||||||
- focus.z as f32)
|
.unwrap_or(CONFIG.sea_level)
|
||||||
/ gain as f32
|
- focus.z as f32)
|
||||||
}
|
/ gain as f32
|
||||||
|
}
|
||||||
|
|
||||||
/// Samples a MapSample at a chunk.
|
/// Samples a MapSample at a chunk.
|
||||||
///
|
///
|
||||||
/// You should generally pass a closure over this function into generate
|
/// You should generally pass a closure over this function into generate
|
||||||
/// when constructing a map for the first time.
|
/// when constructing a map for the first time.
|
||||||
/// However, if repeated construction is needed, or alternate base colors
|
/// However, if repeated construction is needed, or alternate base colors
|
||||||
/// are to be used for some reason, one should pass a custom function to
|
/// are to be used for some reason, one should pass a custom function to
|
||||||
/// generate instead (e.g. one that just looks up the color in a cached
|
/// generate instead (e.g. one that just looks up the color in a cached
|
||||||
/// array).
|
/// array).
|
||||||
pub fn sample_pos(&self, sampler: &WorldSim, pos: Vec2<i32>) -> MapSample {
|
pub fn sample_pos(
|
||||||
let MapConfig {
|
config: &MapConfig,
|
||||||
focus,
|
sampler: &WorldSim,
|
||||||
gain,
|
samples: Option<&[Option<ColumnSample>]>,
|
||||||
samples,
|
pos: Vec2<i32>,
|
||||||
|
) -> MapSample {
|
||||||
|
let map_size_lg = config.map_size_lg();
|
||||||
|
let MapConfig {
|
||||||
|
focus,
|
||||||
|
gain,
|
||||||
|
|
||||||
is_basement,
|
is_basement,
|
||||||
is_water,
|
is_water,
|
||||||
is_shaded,
|
is_shaded,
|
||||||
is_temperature,
|
is_temperature,
|
||||||
is_humidity,
|
is_humidity,
|
||||||
// is_debug,
|
// is_debug,
|
||||||
..
|
..
|
||||||
} = *self;
|
} = *config;
|
||||||
|
|
||||||
let true_sea_level = (CONFIG.sea_level as f64 - focus.z) / gain as f64;
|
let true_sea_level = (CONFIG.sea_level as f64 - focus.z) / gain as f64;
|
||||||
|
|
||||||
let (
|
let (
|
||||||
chunk_idx,
|
chunk_idx,
|
||||||
alt,
|
alt,
|
||||||
basement,
|
basement,
|
||||||
water_alt,
|
water_alt,
|
||||||
humidity,
|
humidity,
|
||||||
temperature,
|
temperature,
|
||||||
downhill,
|
downhill,
|
||||||
river_kind,
|
river_kind,
|
||||||
spline_derivative,
|
spline_derivative,
|
||||||
is_path,
|
is_path,
|
||||||
near_site,
|
near_site,
|
||||||
) = sampler
|
) = sampler
|
||||||
.get(pos)
|
.get(pos)
|
||||||
.map(|sample| {
|
.map(|sample| {
|
||||||
(
|
(
|
||||||
Some(vec2_as_uniform_idx(pos)),
|
Some(vec2_as_uniform_idx(map_size_lg, pos)),
|
||||||
sample.alt,
|
sample.alt,
|
||||||
sample.basement,
|
sample.basement,
|
||||||
sample.water_alt,
|
sample.water_alt,
|
||||||
sample.humidity,
|
sample.humidity,
|
||||||
sample.temp,
|
sample.temp,
|
||||||
sample.downhill,
|
sample.downhill,
|
||||||
sample.river.river_kind,
|
sample.river.river_kind,
|
||||||
sample.river.spline_derivative,
|
sample.river.spline_derivative,
|
||||||
sample.path.is_path(),
|
sample.path.is_path(),
|
||||||
sample.sites.iter().any(|site| {
|
sample.sites.iter().any(|site| {
|
||||||
site.get_origin()
|
site.get_origin()
|
||||||
.distance_squared(pos * TerrainChunkSize::RECT_SIZE.x as i32)
|
.distance_squared(pos * TerrainChunkSize::RECT_SIZE.x as i32)
|
||||||
< 64i32.pow(2)
|
< 64i32.pow(2)
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.unwrap_or((
|
.unwrap_or((
|
||||||
None,
|
None,
|
||||||
CONFIG.sea_level,
|
CONFIG.sea_level,
|
||||||
CONFIG.sea_level,
|
CONFIG.sea_level,
|
||||||
CONFIG.sea_level,
|
CONFIG.sea_level,
|
||||||
0.0,
|
0.0,
|
||||||
0.0,
|
0.0,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
Vec2::zero(),
|
Vec2::zero(),
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
));
|
));
|
||||||
|
|
||||||
let humidity = humidity.min(1.0).max(0.0);
|
let humidity = humidity.min(1.0).max(0.0);
|
||||||
let temperature = temperature.min(1.0).max(-1.0) * 0.5 + 0.5;
|
let temperature = temperature.min(1.0).max(-1.0) * 0.5 + 0.5;
|
||||||
let wpos = pos * TerrainChunkSize::RECT_SIZE.map(|e| e as i32);
|
let wpos = pos * TerrainChunkSize::RECT_SIZE.map(|e| e as i32);
|
||||||
let column_rgb = samples
|
let column_rgb = samples
|
||||||
.and_then(|samples| {
|
.and_then(|samples| {
|
||||||
chunk_idx
|
|
||||||
.and_then(|chunk_idx| samples.get(chunk_idx))
|
|
||||||
.map(Option::as_ref)
|
|
||||||
.flatten()
|
|
||||||
})
|
|
||||||
.map(|sample| {
|
|
||||||
// TODO: Eliminate the redundancy between this and the block renderer.
|
|
||||||
let alt = sample.alt;
|
|
||||||
let basement = sample.basement;
|
|
||||||
let grass_depth = (1.5 + 2.0 * sample.chaos).min(alt - basement);
|
|
||||||
let wposz = if is_basement { basement } else { alt };
|
|
||||||
if is_basement && wposz < alt - grass_depth {
|
|
||||||
Lerp::lerp(
|
|
||||||
sample.sub_surface_color,
|
|
||||||
sample.stone_col.map(|e| e as f32 / 255.0),
|
|
||||||
(alt - grass_depth - wposz as f32) * 0.15,
|
|
||||||
)
|
|
||||||
.map(|e| e as f64)
|
|
||||||
} else {
|
|
||||||
Lerp::lerp(
|
|
||||||
sample.sub_surface_color,
|
|
||||||
sample.surface_color,
|
|
||||||
((wposz as f32 - (alt - grass_depth)) / grass_depth).powf(0.5),
|
|
||||||
)
|
|
||||||
.map(|e| e as f64)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let downhill_wpos = downhill
|
|
||||||
.map(|downhill_pos| downhill_pos)
|
|
||||||
.unwrap_or(wpos + TerrainChunkSize::RECT_SIZE.map(|e| e as i32));
|
|
||||||
let alt = if is_basement { basement } else { alt };
|
|
||||||
|
|
||||||
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 alt = true_alt.min(1.0).max(0.0);
|
|
||||||
|
|
||||||
let water_color_factor = 2.0;
|
|
||||||
let g_water = 32.0 * water_color_factor;
|
|
||||||
let b_water = 64.0 * water_color_factor;
|
|
||||||
let default_rgb = Rgb::new(
|
|
||||||
if is_shaded || is_temperature {
|
|
||||||
1.0
|
|
||||||
} else {
|
|
||||||
0.0
|
|
||||||
},
|
|
||||||
if is_shaded { 1.0 } else { alt },
|
|
||||||
if is_shaded || is_humidity { 1.0 } else { 0.0 },
|
|
||||||
);
|
|
||||||
let column_rgb = column_rgb.unwrap_or(default_rgb);
|
|
||||||
let mut connections = [None; 8];
|
|
||||||
let mut has_connections = false;
|
|
||||||
// TODO: Support non-river connections.
|
|
||||||
// TODO: Support multiple connections.
|
|
||||||
let river_width = river_kind.map(|river| match river {
|
|
||||||
RiverKind::River { cross_section } => cross_section.x,
|
|
||||||
RiverKind::Lake { .. } | RiverKind::Ocean => TerrainChunkSize::RECT_SIZE.x as f32,
|
|
||||||
});
|
|
||||||
if let (Some(river_width), true) = (river_width, is_water) {
|
|
||||||
let downhill_pos = downhill_wpos.map2(TerrainChunkSize::RECT_SIZE, |e, f| e / f as i32);
|
|
||||||
NEIGHBOR_DELTA
|
|
||||||
.iter()
|
|
||||||
.zip((&mut connections).iter_mut())
|
|
||||||
.filter(|&(&offset, _)| downhill_pos - pos == Vec2::from(offset))
|
|
||||||
.for_each(|(_, connection)| {
|
|
||||||
has_connections = true;
|
|
||||||
*connection = Some(Connection {
|
|
||||||
kind: ConnectionKind::River,
|
|
||||||
spline_derivative,
|
|
||||||
width: river_width,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
let rgb = match (river_kind, (is_water, true_alt >= true_sea_level)) {
|
|
||||||
(_, (false, _)) | (None, (_, true)) | (Some(RiverKind::River { .. }), _) => {
|
|
||||||
let (r, g, b) = (
|
|
||||||
(column_rgb.r
|
|
||||||
* if is_temperature {
|
|
||||||
temperature as f64
|
|
||||||
} else {
|
|
||||||
column_rgb.r
|
|
||||||
})
|
|
||||||
.sqrt(),
|
|
||||||
column_rgb.g,
|
|
||||||
(column_rgb.b
|
|
||||||
* if is_humidity {
|
|
||||||
humidity as f64
|
|
||||||
} else {
|
|
||||||
column_rgb.b
|
|
||||||
})
|
|
||||||
.sqrt(),
|
|
||||||
);
|
|
||||||
Rgb::new((r * 255.0) as u8, (g * 255.0) as u8, (b * 255.0) as u8)
|
|
||||||
},
|
|
||||||
(None, _) | (Some(RiverKind::Lake { .. }), _) | (Some(RiverKind::Ocean), _) => {
|
|
||||||
Rgb::new(
|
|
||||||
0,
|
|
||||||
((g_water - water_depth * g_water) * 1.0) as u8,
|
|
||||||
((b_water - water_depth * b_water) * 1.0) as u8,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
};
|
|
||||||
// TODO: Make principled.
|
|
||||||
let rgb = if near_site {
|
|
||||||
Rgb::new(0x57, 0x39, 0x33)
|
|
||||||
} else if is_path {
|
|
||||||
Rgb::new(0x37, 0x29, 0x23)
|
|
||||||
} else {
|
|
||||||
rgb
|
|
||||||
};
|
|
||||||
|
|
||||||
MapSample {
|
|
||||||
rgb,
|
|
||||||
alt: if is_water {
|
|
||||||
true_alt.max(true_water_alt)
|
|
||||||
} else {
|
|
||||||
true_alt
|
|
||||||
},
|
|
||||||
downhill_wpos,
|
|
||||||
connections: if has_connections {
|
|
||||||
Some(connections)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Generates a map image using the specified settings. Note that it will
|
|
||||||
/// write from left to write from (0, 0) to dimensions - 1, inclusive,
|
|
||||||
/// with 4 1-byte color components provided as (r, g, b, a). It is up
|
|
||||||
/// to the caller to provide a function that translates this information
|
|
||||||
/// into the correct format for a buffer and writes to it.
|
|
||||||
///
|
|
||||||
/// sample_pos is a function that, given a chunk position, returns enough
|
|
||||||
/// information about the chunk to attempt to render it on the map.
|
|
||||||
/// When in doubt, try using `MapConfig::sample_pos` for this.
|
|
||||||
///
|
|
||||||
/// sample_wpos is a simple function that, given a *column* position,
|
|
||||||
/// returns the approximate altitude at that column. When in doubt, try
|
|
||||||
/// using `MapConfig::sample_wpos` for this.
|
|
||||||
#[allow(clippy::if_same_then_else)] // TODO: Pending review in #587
|
|
||||||
#[allow(clippy::unnested_or_patterns)] // TODO: Pending review in #587
|
|
||||||
#[allow(clippy::many_single_char_names)]
|
|
||||||
pub fn generate(
|
|
||||||
&self,
|
|
||||||
sample_pos: impl Fn(Vec2<i32>) -> MapSample,
|
|
||||||
sample_wpos: impl Fn(Vec2<i32>) -> f32,
|
|
||||||
// sampler: &WorldSim,
|
|
||||||
mut write_pixel: impl FnMut(Vec2<usize>, (u8, u8, u8, u8)),
|
|
||||||
) -> MapDebug {
|
|
||||||
let MapConfig {
|
|
||||||
dimensions,
|
|
||||||
focus,
|
|
||||||
gain,
|
|
||||||
lgain,
|
|
||||||
scale,
|
|
||||||
light_direction,
|
|
||||||
horizons,
|
|
||||||
|
|
||||||
is_shaded,
|
|
||||||
// is_debug,
|
|
||||||
..
|
|
||||||
} = *self;
|
|
||||||
|
|
||||||
let light_direction = Vec3::new(
|
|
||||||
light_direction.x,
|
|
||||||
light_direction.y,
|
|
||||||
0.0, // we currently ignore light_direction.z.
|
|
||||||
);
|
|
||||||
let light_shadow_dir = if light_direction.x >= 0.0 { 0 } else { 1 };
|
|
||||||
let horizon_map = horizons.map(|horizons| &horizons[light_shadow_dir]);
|
|
||||||
let light = light_direction.normalized();
|
|
||||||
let /*mut */quads = [[0u32; QUADRANTS]; QUADRANTS];
|
|
||||||
let /*mut */rivers = 0u32;
|
|
||||||
let /*mut */lakes = 0u32;
|
|
||||||
let /*mut */oceans = 0u32;
|
|
||||||
|
|
||||||
let focus_rect = Vec2::from(focus);
|
|
||||||
|
|
||||||
let chunk_size = TerrainChunkSize::RECT_SIZE.map(|e| e as f64);
|
|
||||||
|
|
||||||
(0..dimensions.y * dimensions.x).for_each(|chunk_idx| {
|
|
||||||
let i = chunk_idx % dimensions.x as usize;
|
|
||||||
let j = chunk_idx / dimensions.x as usize;
|
|
||||||
|
|
||||||
let wposf = focus_rect + Vec2::new(i as f64, j as f64) * scale;
|
|
||||||
let pos = wposf.map(|e: f64| e as i32);
|
|
||||||
let wposf = wposf * chunk_size;
|
|
||||||
|
|
||||||
let chunk_idx = if pos.reduce_partial_min() >= 0
|
|
||||||
&& pos.x < WORLD_SIZE.x as i32
|
|
||||||
&& pos.y < WORLD_SIZE.y as i32
|
|
||||||
{
|
|
||||||
Some(vec2_as_uniform_idx(pos))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
let MapSample {
|
|
||||||
rgb,
|
|
||||||
alt,
|
|
||||||
downhill_wpos,
|
|
||||||
..
|
|
||||||
} = sample_pos(pos);
|
|
||||||
|
|
||||||
let alt = alt as f32;
|
|
||||||
let wposi = pos * TerrainChunkSize::RECT_SIZE.map(|e| e as i32);
|
|
||||||
let mut rgb = rgb.map(|e| e as f64 / 255.0);
|
|
||||||
|
|
||||||
// Material properties:
|
|
||||||
//
|
|
||||||
// For each material in the scene,
|
|
||||||
// k_s = (RGB) specular reflection constant
|
|
||||||
let mut k_s = Rgb::new(1.0, 1.0, 1.0);
|
|
||||||
// k_d = (RGB) diffuse reflection constant
|
|
||||||
let mut k_d = rgb;
|
|
||||||
// k_a = (RGB) ambient reflection constant
|
|
||||||
let mut k_a = rgb;
|
|
||||||
// α = (per-material) shininess constant
|
|
||||||
let mut alpha = 4.0; // 4.0;
|
|
||||||
|
|
||||||
// Compute connections
|
|
||||||
let mut has_river = false;
|
|
||||||
// NOTE: consider replacing neighbors with local_cells, since it is more
|
|
||||||
// accurate (though I'm not sure if it can matter for these
|
|
||||||
// purposes).
|
|
||||||
chunk_idx
|
chunk_idx
|
||||||
.map(|chunk_idx| neighbors(chunk_idx).chain(iter::once(chunk_idx)))
|
.and_then(|chunk_idx| samples.get(chunk_idx))
|
||||||
.into_iter()
|
.map(Option::as_ref)
|
||||||
.flatten()
|
.flatten()
|
||||||
.for_each(|neighbor_posi| {
|
})
|
||||||
let neighbor_pos = uniform_idx_as_vec2(neighbor_posi);
|
.map(|sample| {
|
||||||
let neighbor_wpos = neighbor_pos.map(|e| e as f64) * chunk_size;
|
// TODO: Eliminate the redundancy between this and the block renderer.
|
||||||
let MapSample { connections, .. } = sample_pos(neighbor_pos);
|
let alt = sample.alt;
|
||||||
NEIGHBOR_DELTA
|
let basement = sample.basement;
|
||||||
.iter()
|
let grass_depth = (1.5 + 2.0 * sample.chaos).min(alt - basement);
|
||||||
.zip(
|
let wposz = if is_basement { basement } else { alt };
|
||||||
connections
|
if is_basement && wposz < alt - grass_depth {
|
||||||
.as_ref()
|
Lerp::lerp(
|
||||||
.map(|e| e.iter())
|
sample.sub_surface_color,
|
||||||
.into_iter()
|
sample.stone_col.map(|e| e as f32 / 255.0),
|
||||||
.flatten()
|
(alt - grass_depth - wposz as f32) * 0.15,
|
||||||
.into_iter(),
|
)
|
||||||
)
|
.map(|e| e as f64)
|
||||||
.for_each(|(&delta, connection)| {
|
|
||||||
let connection = if let Some(connection) = connection {
|
|
||||||
connection
|
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
let downhill_wpos = neighbor_wpos
|
|
||||||
+ Vec2::from(delta).map(|e: i32| e as f64) * chunk_size;
|
|
||||||
let coeffs = river_spline_coeffs(
|
|
||||||
neighbor_wpos,
|
|
||||||
connection.spline_derivative,
|
|
||||||
downhill_wpos,
|
|
||||||
);
|
|
||||||
let (_t, _pt, dist) = if let Some((t, pt, dist)) =
|
|
||||||
quadratic_nearest_point(&coeffs, wposf)
|
|
||||||
{
|
|
||||||
(t, pt, dist)
|
|
||||||
} else {
|
|
||||||
let ndist = wposf.distance_squared(neighbor_wpos);
|
|
||||||
let ddist = wposf.distance_squared(downhill_wpos);
|
|
||||||
if ndist <= ddist {
|
|
||||||
(0.0, neighbor_wpos, ndist)
|
|
||||||
} else {
|
|
||||||
(1.0, downhill_wpos, ddist)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let connection_dist =
|
|
||||||
(dist.sqrt() - (connection.width as f64 * 0.5).max(1.0)).max(0.0);
|
|
||||||
if connection_dist == 0.0 {
|
|
||||||
match connection.kind {
|
|
||||||
ConnectionKind::River => {
|
|
||||||
has_river = true;
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Color in connectins.
|
|
||||||
let water_color_factor = 2.0;
|
|
||||||
let g_water = 32.0 * water_color_factor;
|
|
||||||
let b_water = 64.0 * water_color_factor;
|
|
||||||
if has_river {
|
|
||||||
let water_rgb = Rgb::new(0, ((g_water) * 1.0) as u8, ((b_water) * 1.0) as u8)
|
|
||||||
.map(|e| e as f64 / 255.0);
|
|
||||||
rgb = water_rgb;
|
|
||||||
k_s = Rgb::new(1.0, 1.0, 1.0);
|
|
||||||
k_d = water_rgb;
|
|
||||||
k_a = water_rgb;
|
|
||||||
alpha = 0.255;
|
|
||||||
}
|
|
||||||
|
|
||||||
let downhill_alt = sample_wpos(downhill_wpos);
|
|
||||||
let cross_pos = wposi
|
|
||||||
+ ((downhill_wpos - wposi)
|
|
||||||
.map(|e| e as f32)
|
|
||||||
.rotated_z(f32::consts::FRAC_PI_2)
|
|
||||||
.map(|e| e as i32));
|
|
||||||
let cross_alt = sample_wpos(cross_pos);
|
|
||||||
// Pointing downhill, forward
|
|
||||||
// (index--note that (0,0,1) is backward right-handed)
|
|
||||||
let forward_vec = Vec3::new(
|
|
||||||
(downhill_wpos.x - wposi.x) as f64,
|
|
||||||
((downhill_alt - alt) * gain) as f64 * lgain,
|
|
||||||
(downhill_wpos.y - wposi.y) 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 - wposi.x) as f64,
|
|
||||||
((cross_alt - alt) * gain) as f64 * lgain,
|
|
||||||
(cross_pos.y - wposi.y) as f64,
|
|
||||||
);
|
|
||||||
// let surface_normal = Vec3::new(lgain * (f.y * u.z - f.z * u.y), -(f.x * u.z -
|
|
||||||
// f.z * u.x), lgain * (f.x * u.y - f.y * u.x)).normalized();
|
|
||||||
// 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();
|
|
||||||
|
|
||||||
// TODO: Figure out if we can reimplement debugging.
|
|
||||||
/* if is_debug {
|
|
||||||
let quad =
|
|
||||||
|x: f32| ((x as f64 * QUADRANTS as f64).floor() as usize).min(QUADRANTS - 1);
|
|
||||||
if river_kind.is_none() || humidity != 0.0 {
|
|
||||||
quads[quad(humidity)][quad(temperature)] += 1;
|
|
||||||
}
|
|
||||||
match river_kind {
|
|
||||||
Some(RiverKind::River { .. }) => {
|
|
||||||
rivers += 1;
|
|
||||||
},
|
|
||||||
Some(RiverKind::Lake { .. }) => {
|
|
||||||
lakes += 1;
|
|
||||||
},
|
|
||||||
Some(RiverKind::Ocean { .. }) => {
|
|
||||||
oceans += 1;
|
|
||||||
},
|
|
||||||
None => {},
|
|
||||||
}
|
|
||||||
} */
|
|
||||||
|
|
||||||
let shade_frac = horizon_map
|
|
||||||
.and_then(|(angles, heights)| {
|
|
||||||
chunk_idx
|
|
||||||
.and_then(|chunk_idx| angles.get(chunk_idx))
|
|
||||||
.map(|&e| (e as f64, heights))
|
|
||||||
})
|
|
||||||
.and_then(|(e, heights)| {
|
|
||||||
chunk_idx
|
|
||||||
.and_then(|chunk_idx| heights.get(chunk_idx))
|
|
||||||
.map(|&f| (e, f as f64))
|
|
||||||
})
|
|
||||||
.map(|(angle, height)| {
|
|
||||||
let w = 0.1;
|
|
||||||
let height = (height - alt as Alt * gain as Alt).max(0.0);
|
|
||||||
if angle != 0.0 && light_direction.x != 0.0 && height != 0.0 {
|
|
||||||
let deltax = height / angle;
|
|
||||||
let lighty = (light_direction.y / light_direction.x * deltax).abs();
|
|
||||||
let deltay = lighty - height;
|
|
||||||
let s = (deltay / deltax / w).min(1.0).max(0.0);
|
|
||||||
// Smoothstep
|
|
||||||
s * s * (3.0 - 2.0 * s)
|
|
||||||
} else {
|
|
||||||
1.0
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.unwrap_or(1.0);
|
|
||||||
|
|
||||||
let rgb = if is_shaded {
|
|
||||||
// Phong reflection model with shadows:
|
|
||||||
//
|
|
||||||
// I_p = k_a i_a + shadow * Σ {m ∈ lights} (k_d (L_m ⋅ N) i_m,d + k_s (R_m ⋅
|
|
||||||
// V)^α i_m,s)
|
|
||||||
//
|
|
||||||
// where for the whole scene,
|
|
||||||
// i_a = (RGB) intensity of ambient lighting component
|
|
||||||
let i_a = Rgb::new(0.1, 0.1, 0.1);
|
|
||||||
// V = direction pointing towards the viewer (e.g. virtual camera).
|
|
||||||
let v = Vec3::new(0.0, 0.0, -1.0).normalized();
|
|
||||||
// let v = Vec3::new(0.0, -1.0, 0.0).normalized();
|
|
||||||
//
|
|
||||||
// for each light m,
|
|
||||||
// i_m,d = (RGB) intensity of diffuse component of light source m
|
|
||||||
let i_m_d = Rgb::new(1.0, 1.0, 1.0);
|
|
||||||
// i_m,s = (RGB) intensity of specular component of light source m
|
|
||||||
let i_m_s = Rgb::new(0.45, 0.45, 0.45);
|
|
||||||
// let i_m_s = Rgb::new(0.45, 0.45, 0.45);
|
|
||||||
|
|
||||||
// for each light m and point p,
|
|
||||||
// L_m = (normalized) direction vector from point on surface to light source m
|
|
||||||
let l_m = light;
|
|
||||||
// N = (normalized) normal at this point on the surface,
|
|
||||||
let n = surface_normal;
|
|
||||||
// R_m = (normalized) direction a perfectly reflected ray of light from m would
|
|
||||||
// take from point p = 2(L_m ⋅ N)N - L_m
|
|
||||||
let r_m = (-l_m).reflected(n); // 2 * (l_m.dot(n)) * n - l_m;
|
|
||||||
//
|
|
||||||
// and for each point p in the scene,
|
|
||||||
// shadow = computed shadow factor at point p
|
|
||||||
// FIXME: Should really just be shade_frac, but with only ambient light we lose
|
|
||||||
// all local lighting detail... some sort of global illumination (e.g.
|
|
||||||
// radiosity) is of course the "right" solution, but maybe we can find
|
|
||||||
// something cheaper?
|
|
||||||
let shadow = 0.2 + 0.8 * shade_frac;
|
|
||||||
|
|
||||||
let lambertian = l_m.dot(n).max(0.0);
|
|
||||||
let spec_angle = r_m.dot(v).max(0.0);
|
|
||||||
|
|
||||||
let ambient = k_a * i_a;
|
|
||||||
let diffuse = k_d * lambertian * i_m_d;
|
|
||||||
let specular = k_s * spec_angle.powf(alpha) * i_m_s;
|
|
||||||
(ambient + shadow * (diffuse + specular)).map(|e| e.min(1.0))
|
|
||||||
} else {
|
} else {
|
||||||
rgb
|
Lerp::lerp(
|
||||||
|
sample.sub_surface_color,
|
||||||
|
sample.surface_color,
|
||||||
|
((wposz as f32 - (alt - grass_depth)) / grass_depth).powf(0.5),
|
||||||
|
)
|
||||||
|
.map(|e| e as f64)
|
||||||
}
|
}
|
||||||
.map(|e| (e * 255.0) as u8);
|
|
||||||
|
|
||||||
let rgba = (rgb.r, rgb.g, rgb.b, 255);
|
|
||||||
write_pixel(Vec2::new(i, j), rgba);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
MapDebug {
|
let downhill_wpos = downhill
|
||||||
quads,
|
.map(|downhill_pos| downhill_pos)
|
||||||
rivers,
|
.unwrap_or(wpos + TerrainChunkSize::RECT_SIZE.map(|e| e as i32));
|
||||||
lakes,
|
let alt = if is_basement { basement } else { alt };
|
||||||
oceans,
|
|
||||||
}
|
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 alt = true_alt.min(1.0).max(0.0);
|
||||||
|
|
||||||
|
let water_color_factor = 2.0;
|
||||||
|
let g_water = 32.0 * water_color_factor;
|
||||||
|
let b_water = 64.0 * water_color_factor;
|
||||||
|
let default_rgb = Rgb::new(
|
||||||
|
if is_shaded || is_temperature {
|
||||||
|
1.0
|
||||||
|
} else {
|
||||||
|
0.0
|
||||||
|
},
|
||||||
|
if is_shaded { 1.0 } else { alt },
|
||||||
|
if is_shaded || is_humidity { 1.0 } else { 0.0 },
|
||||||
|
);
|
||||||
|
let column_rgb = column_rgb.unwrap_or(default_rgb);
|
||||||
|
let mut connections = [None; 8];
|
||||||
|
let mut has_connections = false;
|
||||||
|
// TODO: Support non-river connections.
|
||||||
|
// TODO: Support multiple connections.
|
||||||
|
let river_width = river_kind.map(|river| match river {
|
||||||
|
RiverKind::River { cross_section } => cross_section.x,
|
||||||
|
RiverKind::Lake { .. } | RiverKind::Ocean => TerrainChunkSize::RECT_SIZE.x as f32,
|
||||||
|
});
|
||||||
|
if let (Some(river_width), true) = (river_width, is_water) {
|
||||||
|
let downhill_pos = downhill_wpos.map2(TerrainChunkSize::RECT_SIZE, |e, f| e / f as i32);
|
||||||
|
NEIGHBOR_DELTA
|
||||||
|
.iter()
|
||||||
|
.zip((&mut connections).iter_mut())
|
||||||
|
.filter(|&(&offset, _)| downhill_pos - pos == Vec2::from(offset))
|
||||||
|
.for_each(|(_, connection)| {
|
||||||
|
has_connections = true;
|
||||||
|
*connection = Some(Connection {
|
||||||
|
kind: ConnectionKind::River,
|
||||||
|
spline_derivative,
|
||||||
|
width: river_width,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
let rgb = match (river_kind, (is_water, true_alt >= true_sea_level)) {
|
||||||
|
(_, (false, _)) | (None, (_, true)) | (Some(RiverKind::River { .. }), _) => {
|
||||||
|
let (r, g, b) = (
|
||||||
|
(column_rgb.r
|
||||||
|
* if is_temperature {
|
||||||
|
temperature as f64
|
||||||
|
} else {
|
||||||
|
column_rgb.r
|
||||||
|
})
|
||||||
|
.sqrt(),
|
||||||
|
column_rgb.g,
|
||||||
|
(column_rgb.b
|
||||||
|
* if is_humidity {
|
||||||
|
humidity as f64
|
||||||
|
} else {
|
||||||
|
column_rgb.b
|
||||||
|
})
|
||||||
|
.sqrt(),
|
||||||
|
);
|
||||||
|
Rgb::new((r * 255.0) as u8, (g * 255.0) as u8, (b * 255.0) as u8)
|
||||||
|
},
|
||||||
|
(None, _) | (Some(RiverKind::Lake { .. }), _) | (Some(RiverKind::Ocean), _) => Rgb::new(
|
||||||
|
0,
|
||||||
|
((g_water - water_depth * g_water) * 1.0) as u8,
|
||||||
|
((b_water - water_depth * b_water) * 1.0) as u8,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
// TODO: Make principled.
|
||||||
|
let rgb = if near_site {
|
||||||
|
Rgb::new(0x57, 0x39, 0x33)
|
||||||
|
} else if is_path {
|
||||||
|
Rgb::new(0x37, 0x29, 0x23)
|
||||||
|
} else {
|
||||||
|
rgb
|
||||||
|
};
|
||||||
|
|
||||||
|
MapSample {
|
||||||
|
rgb,
|
||||||
|
alt: if is_water {
|
||||||
|
true_alt.max(true_water_alt)
|
||||||
|
} else {
|
||||||
|
true_alt
|
||||||
|
},
|
||||||
|
downhill_wpos,
|
||||||
|
connections: if has_connections {
|
||||||
|
Some(connections)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,12 +14,11 @@ pub use self::{
|
|||||||
get_rivers, mrec_downhill, Alt, RiverData, RiverKind,
|
get_rivers, mrec_downhill, Alt, RiverData, RiverKind,
|
||||||
},
|
},
|
||||||
location::Location,
|
location::Location,
|
||||||
map::{MapConfig, MapDebug, MapSample},
|
map::{sample_pos, sample_wpos},
|
||||||
path::PathData,
|
path::PathData,
|
||||||
util::{
|
util::{
|
||||||
cdf_irwin_hall, downhill, get_horizon_map, get_oceans, local_cells, map_edge_factor,
|
cdf_irwin_hall, downhill, get_horizon_map, get_oceans, local_cells, map_edge_factor,
|
||||||
neighbors, uniform_idx_as_vec2, uniform_noise, uphill, vec2_as_uniform_idx, InverseCdf,
|
uniform_noise, uphill, InverseCdf, ScaleBias,
|
||||||
ScaleBias, NEIGHBOR_DELTA,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -36,7 +35,10 @@ use common::{
|
|||||||
assets,
|
assets,
|
||||||
msg::server::WorldMapMsg,
|
msg::server::WorldMapMsg,
|
||||||
store::Id,
|
store::Id,
|
||||||
terrain::{BiomeKind, TerrainChunkSize},
|
terrain::{
|
||||||
|
map::MapConfig, uniform_idx_as_vec2, vec2_as_uniform_idx, BiomeKind, MapSizeLg,
|
||||||
|
TerrainChunkSize,
|
||||||
|
},
|
||||||
vol::RectVolSize,
|
vol::RectVolSize,
|
||||||
};
|
};
|
||||||
use noise::{
|
use noise::{
|
||||||
@ -58,6 +60,18 @@ use std::{
|
|||||||
use tracing::{debug, warn};
|
use tracing::{debug, warn};
|
||||||
use vek::*;
|
use vek::*;
|
||||||
|
|
||||||
|
/// Default base two logarithm of the world size, in chunks, per dimension.
|
||||||
|
///
|
||||||
|
/// Currently, our default map dimensions are 2^10 × 2^10 chunks,
|
||||||
|
/// mostly for historical reasons. It is likely that we will increase this
|
||||||
|
/// default at some point.
|
||||||
|
const DEFAULT_WORLD_CHUNKS_LG: MapSizeLg =
|
||||||
|
if let Ok(map_size_lg) = MapSizeLg::new(Vec2 { x: 10, y: 10 }) {
|
||||||
|
map_size_lg
|
||||||
|
} else {
|
||||||
|
panic!("Default world chunk size does not satisfy required invariants.");
|
||||||
|
};
|
||||||
|
|
||||||
// NOTE: I suspect this is too small (1024 * 16 * 1024 * 16 * 8 doesn't fit in
|
// NOTE: I suspect this is too small (1024 * 16 * 1024 * 16 * 8 doesn't fit in
|
||||||
// an i32), but we'll see what happens, I guess! We could always store sizes >>
|
// an i32), but we'll see what happens, I guess! We could always store sizes >>
|
||||||
// 3. I think 32 or 64 is the absolute limit though, and would require
|
// 3. I think 32 or 64 is the absolute limit though, and would require
|
||||||
@ -67,9 +81,9 @@ use vek::*;
|
|||||||
// don't think we actually cast a chunk id to float, just coordinates... could
|
// don't think we actually cast a chunk id to float, just coordinates... could
|
||||||
// be wrong though!
|
// be wrong though!
|
||||||
#[allow(clippy::identity_op)] // TODO: Pending review in #587
|
#[allow(clippy::identity_op)] // TODO: Pending review in #587
|
||||||
pub const WORLD_SIZE: Vec2<usize> = Vec2 {
|
const WORLD_SIZE: Vec2<usize> = Vec2 {
|
||||||
x: 1024 * 1,
|
x: 1 << DEFAULT_WORLD_CHUNKS_LG.vec().x,
|
||||||
y: 1024 * 1,
|
y: 1 << DEFAULT_WORLD_CHUNKS_LG.vec().y,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A structure that holds cached noise values and cumulative distribution
|
/// A structure that holds cached noise values and cumulative distribution
|
||||||
@ -296,6 +310,8 @@ impl WorldFile {
|
|||||||
|
|
||||||
pub struct WorldSim {
|
pub struct WorldSim {
|
||||||
pub seed: u32,
|
pub seed: u32,
|
||||||
|
/// Base 2 logarithm of the map size.
|
||||||
|
map_size_lg: MapSizeLg,
|
||||||
/// Maximum height above sea level of any chunk in the map (not including
|
/// Maximum height above sea level of any chunk in the map (not including
|
||||||
/// post-erosion warping, cliffs, and other things like that).
|
/// post-erosion warping, cliffs, and other things like that).
|
||||||
pub max_height: f32,
|
pub max_height: f32,
|
||||||
@ -449,16 +465,17 @@ impl WorldSim {
|
|||||||
// Assumes μ = 0, σ = 1
|
// Assumes μ = 0, σ = 1
|
||||||
let logistic_cdf = |x: f64| (x / logistic_2_base).tanh() * 0.5 + 0.5;
|
let logistic_cdf = |x: f64| (x / logistic_2_base).tanh() * 0.5 + 0.5;
|
||||||
|
|
||||||
let min_epsilon =
|
let map_size_lg = DEFAULT_WORLD_CHUNKS_LG;
|
||||||
1.0 / (WORLD_SIZE.x as f64 * WORLD_SIZE.y as f64).max(f64::EPSILON as f64 * 0.5);
|
let map_size_chunks_len_f64 = map_size_lg.chunks().map(f64::from).product();
|
||||||
let max_epsilon = (1.0 - 1.0 / (WORLD_SIZE.x as f64 * WORLD_SIZE.y as f64))
|
let min_epsilon = 1.0 / map_size_chunks_len_f64.max(f64::EPSILON as f64 * 0.5);
|
||||||
.min(1.0 - f64::EPSILON as f64 * 0.5);
|
let max_epsilon =
|
||||||
|
(1.0 - 1.0 / map_size_chunks_len_f64).min(1.0 - f64::EPSILON as f64 * 0.5);
|
||||||
|
|
||||||
// No NaNs in these uniform vectors, since the original noise value always
|
// No NaNs in these uniform vectors, since the original noise value always
|
||||||
// returns Some.
|
// returns Some.
|
||||||
let ((alt_base, _), (chaos, _)) = rayon::join(
|
let ((alt_base, _), (chaos, _)) = rayon::join(
|
||||||
|| {
|
|| {
|
||||||
uniform_noise(|_, wposf| {
|
uniform_noise(map_size_lg, |_, wposf| {
|
||||||
// "Base" of the chunk, to be multiplied by CONFIG.mountain_scale (multiplied
|
// "Base" of the chunk, to be multiplied by CONFIG.mountain_scale (multiplied
|
||||||
// value is from -0.35 * (CONFIG.mountain_scale * 1.05) to
|
// value is from -0.35 * (CONFIG.mountain_scale * 1.05) to
|
||||||
// 0.35 * (CONFIG.mountain_scale * 0.95), but value here is from -0.3675 to
|
// 0.35 * (CONFIG.mountain_scale * 0.95), but value here is from -0.3675 to
|
||||||
@ -475,7 +492,7 @@ impl WorldSim {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
|| {
|
|| {
|
||||||
uniform_noise(|_, wposf| {
|
uniform_noise(map_size_lg, |_, wposf| {
|
||||||
// From 0 to 1.6, but the distribution before the max is from -1 and 1.6, so
|
// From 0 to 1.6, but the distribution before the max is from -1 and 1.6, so
|
||||||
// there is a 50% chance that hill will end up at 0.3 or
|
// there is a 50% chance that hill will end up at 0.3 or
|
||||||
// lower, and probably a very high change it will be exactly
|
// lower, and probably a very high change it will be exactly
|
||||||
@ -548,7 +565,7 @@ impl WorldSim {
|
|||||||
//
|
//
|
||||||
// No NaNs in these uniform vectors, since the original noise value always
|
// No NaNs in these uniform vectors, since the original noise value always
|
||||||
// returns Some.
|
// returns Some.
|
||||||
let (alt_old, _) = uniform_noise(|posi, wposf| {
|
let (alt_old, _) = uniform_noise(map_size_lg, |posi, wposf| {
|
||||||
// This is the extension upwards from the base added to some extra noise from -1
|
// This is the extension upwards from the base added to some extra noise from -1
|
||||||
// to 1.
|
// to 1.
|
||||||
//
|
//
|
||||||
@ -612,11 +629,11 @@ impl WorldSim {
|
|||||||
// = [-0.946, 1.067]
|
// = [-0.946, 1.067]
|
||||||
Some(
|
Some(
|
||||||
((alt_base[posi].1 + alt_main.mul((chaos[posi].1 as f64).powf(1.2)))
|
((alt_base[posi].1 + alt_main.mul((chaos[posi].1 as f64).powf(1.2)))
|
||||||
.mul(map_edge_factor(posi) as f64)
|
.mul(map_edge_factor(map_size_lg, posi) as f64)
|
||||||
.add(
|
.add(
|
||||||
(CONFIG.sea_level as f64)
|
(CONFIG.sea_level as f64)
|
||||||
.div(CONFIG.mountain_scale as f64)
|
.div(CONFIG.mountain_scale as f64)
|
||||||
.mul(map_edge_factor(posi) as f64),
|
.mul(map_edge_factor(map_size_lg, posi) as f64),
|
||||||
)
|
)
|
||||||
.sub((CONFIG.sea_level as f64).div(CONFIG.mountain_scale as f64)))
|
.sub((CONFIG.sea_level as f64).div(CONFIG.mountain_scale as f64)))
|
||||||
as f32,
|
as f32,
|
||||||
@ -624,12 +641,12 @@ impl WorldSim {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Calculate oceans.
|
// Calculate oceans.
|
||||||
let is_ocean = get_oceans(|posi: usize| alt_old[posi].1);
|
let is_ocean = get_oceans(map_size_lg, |posi: usize| alt_old[posi].1);
|
||||||
// NOTE: Uncomment if you want oceans to exclusively be on the border of the
|
// NOTE: Uncomment if you want oceans to exclusively be on the border of the
|
||||||
// map.
|
// map.
|
||||||
/* let is_ocean = (0..WORLD_SIZE.x * WORLD_SIZE.y)
|
/* let is_ocean = (0..map_size_lg.chunks())
|
||||||
.into_par_iter()
|
.into_par_iter()
|
||||||
.map(|i| map_edge_factor(i) == 0.0)
|
.map(|i| map_edge_factor(map_size_lg, i) == 0.0)
|
||||||
.collect::<Vec<_>>(); */
|
.collect::<Vec<_>>(); */
|
||||||
let is_ocean_fn = |posi: usize| is_ocean[posi];
|
let is_ocean_fn = |posi: usize| is_ocean[posi];
|
||||||
|
|
||||||
@ -650,14 +667,14 @@ impl WorldSim {
|
|||||||
|
|
||||||
// Recalculate altitudes without oceans.
|
// Recalculate altitudes without oceans.
|
||||||
// NaNs in these uniform vectors wherever is_ocean_fn returns true.
|
// NaNs in these uniform vectors wherever is_ocean_fn returns true.
|
||||||
let (alt_old_no_ocean, _) = uniform_noise(|posi, _| {
|
let (alt_old_no_ocean, _) = uniform_noise(map_size_lg, |posi, _| {
|
||||||
if is_ocean_fn(posi) {
|
if is_ocean_fn(posi) {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
Some(old_height(posi))
|
Some(old_height(posi))
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
let (uplift_uniform, _) = uniform_noise(|posi, _wposf| {
|
let (uplift_uniform, _) = uniform_noise(map_size_lg, |posi, _wposf| {
|
||||||
if is_ocean_fn(posi) {
|
if is_ocean_fn(posi) {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
@ -717,7 +734,7 @@ impl WorldSim {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
let g_func = |posi| {
|
let g_func = |posi| {
|
||||||
if map_edge_factor(posi) == 0.0 {
|
if map_edge_factor(map_size_lg, posi) == 0.0 {
|
||||||
return 0.0;
|
return 0.0;
|
||||||
}
|
}
|
||||||
// G = d* v_s / p_0, where
|
// G = d* v_s / p_0, where
|
||||||
@ -743,8 +760,9 @@ impl WorldSim {
|
|||||||
let epsilon_0_i = 2.078e-3 / 4.0;
|
let epsilon_0_i = 2.078e-3 / 4.0;
|
||||||
return epsilon_0_i * epsilon_0_scale_i;
|
return epsilon_0_i * epsilon_0_scale_i;
|
||||||
}
|
}
|
||||||
let wposf = (uniform_idx_as_vec2(posi) * TerrainChunkSize::RECT_SIZE.map(|e| e as i32))
|
let wposf = (uniform_idx_as_vec2(map_size_lg, posi)
|
||||||
.map(|e| e as f64);
|
* TerrainChunkSize::RECT_SIZE.map(|e| e as i32))
|
||||||
|
.map(|e| e as f64);
|
||||||
let turb_wposf = wposf
|
let turb_wposf = wposf
|
||||||
.mul(5_000.0 / continent_scale)
|
.mul(5_000.0 / continent_scale)
|
||||||
.div(TerrainChunkSize::RECT_SIZE.map(|e| e as f64))
|
.div(TerrainChunkSize::RECT_SIZE.map(|e| e as f64))
|
||||||
@ -797,8 +815,9 @@ impl WorldSim {
|
|||||||
// marine: α = 3.7e-2
|
// marine: α = 3.7e-2
|
||||||
return 3.7e-2 * alpha_scale_i;
|
return 3.7e-2 * alpha_scale_i;
|
||||||
}
|
}
|
||||||
let wposf = (uniform_idx_as_vec2(posi) * TerrainChunkSize::RECT_SIZE.map(|e| e as i32))
|
let wposf = (uniform_idx_as_vec2(map_size_lg, posi)
|
||||||
.map(|e| e as f64);
|
* TerrainChunkSize::RECT_SIZE.map(|e| e as i32))
|
||||||
|
.map(|e| e as f64);
|
||||||
let turb_wposf = wposf
|
let turb_wposf = wposf
|
||||||
.mul(5_000.0 / continent_scale)
|
.mul(5_000.0 / continent_scale)
|
||||||
.div(TerrainChunkSize::RECT_SIZE.map(|e| e as f64))
|
.div(TerrainChunkSize::RECT_SIZE.map(|e| e as f64))
|
||||||
@ -963,10 +982,11 @@ impl WorldSim {
|
|||||||
|
|
||||||
// Perform some erosion.
|
// Perform some erosion.
|
||||||
|
|
||||||
let (alt, basement) = if let Some(map) = parsed_world_file {
|
let (alt, basement, map_size_lg) = if let Some(map) = parsed_world_file {
|
||||||
(map.alt, map.basement)
|
(map.alt, map.basement, DEFAULT_WORLD_CHUNKS_LG)
|
||||||
} else {
|
} else {
|
||||||
let (alt, basement) = do_erosion(
|
let (alt, basement) = do_erosion(
|
||||||
|
map_size_lg,
|
||||||
max_erosion_per_delta_t as f32,
|
max_erosion_per_delta_t as f32,
|
||||||
n_steps,
|
n_steps,
|
||||||
&river_seed,
|
&river_seed,
|
||||||
@ -992,7 +1012,8 @@ impl WorldSim {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Quick "small scale" erosion cycle in order to lower extreme angles.
|
// Quick "small scale" erosion cycle in order to lower extreme angles.
|
||||||
do_erosion(
|
let (alt, basement) = do_erosion(
|
||||||
|
map_size_lg,
|
||||||
1.0f32,
|
1.0f32,
|
||||||
n_small_steps,
|
n_small_steps,
|
||||||
&river_seed,
|
&river_seed,
|
||||||
@ -1011,7 +1032,9 @@ impl WorldSim {
|
|||||||
height_scale,
|
height_scale,
|
||||||
k_d_scale(n_approx),
|
k_d_scale(n_approx),
|
||||||
k_da_scale,
|
k_da_scale,
|
||||||
)
|
);
|
||||||
|
|
||||||
|
(alt, basement, map_size_lg)
|
||||||
};
|
};
|
||||||
|
|
||||||
// Save map, if necessary.
|
// Save map, if necessary.
|
||||||
@ -1060,6 +1083,7 @@ impl WorldSim {
|
|||||||
(alt, basement)
|
(alt, basement)
|
||||||
} else {
|
} else {
|
||||||
do_erosion(
|
do_erosion(
|
||||||
|
map_size_lg,
|
||||||
1.0f32,
|
1.0f32,
|
||||||
n_post_load_steps,
|
n_post_load_steps,
|
||||||
&river_seed,
|
&river_seed,
|
||||||
@ -1081,28 +1105,31 @@ impl WorldSim {
|
|||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
let is_ocean = get_oceans(|posi| alt[posi]);
|
let is_ocean = get_oceans(map_size_lg, |posi| alt[posi]);
|
||||||
let is_ocean_fn = |posi: usize| is_ocean[posi];
|
let is_ocean_fn = |posi: usize| is_ocean[posi];
|
||||||
let mut dh = downhill(|posi| alt[posi], is_ocean_fn);
|
let mut dh = downhill(map_size_lg, |posi| alt[posi], is_ocean_fn);
|
||||||
let (boundary_len, indirection, water_alt_pos, maxh) = get_lakes(|posi| alt[posi], &mut dh);
|
let (boundary_len, indirection, water_alt_pos, maxh) =
|
||||||
|
get_lakes(map_size_lg, |posi| alt[posi], &mut dh);
|
||||||
debug!(?maxh, "Max height");
|
debug!(?maxh, "Max height");
|
||||||
let (mrec, mstack, mwrec) = {
|
let (mrec, mstack, mwrec) = {
|
||||||
let mut wh = vec![0.0; WORLD_SIZE.x * WORLD_SIZE.y];
|
let mut wh = vec![0.0; map_size_lg.chunks_len()];
|
||||||
get_multi_rec(
|
get_multi_rec(
|
||||||
|
map_size_lg,
|
||||||
|posi| alt[posi],
|
|posi| alt[posi],
|
||||||
&dh,
|
&dh,
|
||||||
&water_alt_pos,
|
&water_alt_pos,
|
||||||
&mut wh,
|
&mut wh,
|
||||||
WORLD_SIZE.x,
|
usize::from(map_size_lg.chunks().x),
|
||||||
WORLD_SIZE.y,
|
usize::from(map_size_lg.chunks().y),
|
||||||
TerrainChunkSize::RECT_SIZE.x as Compute,
|
TerrainChunkSize::RECT_SIZE.x as Compute,
|
||||||
TerrainChunkSize::RECT_SIZE.y as Compute,
|
TerrainChunkSize::RECT_SIZE.y as Compute,
|
||||||
maxh,
|
maxh,
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
let flux_old = get_multi_drainage(&mstack, &mrec, &*mwrec, boundary_len);
|
let flux_old = get_multi_drainage(map_size_lg, &mstack, &mrec, &*mwrec, boundary_len);
|
||||||
// let flux_rivers = get_drainage(&water_alt_pos, &dh, boundary_len);
|
// let flux_rivers = get_drainage(map_size_lg, &water_alt_pos, &dh,
|
||||||
// TODO: Make rivers work with multi-direction flux as well.
|
// boundary_len); TODO: Make rivers work with multi-direction flux as
|
||||||
|
// well.
|
||||||
let flux_rivers = flux_old.clone();
|
let flux_rivers = flux_old.clone();
|
||||||
|
|
||||||
let water_height_initial = |chunk_idx| {
|
let water_height_initial = |chunk_idx| {
|
||||||
@ -1153,13 +1180,20 @@ impl WorldSim {
|
|||||||
// may comment out this line and replace it with the commented-out code
|
// may comment out this line and replace it with the commented-out code
|
||||||
// below; however, there are no guarantees that this
|
// below; however, there are no guarantees that this
|
||||||
// will work correctly.
|
// will work correctly.
|
||||||
let water_alt = fill_sinks(water_height_initial, is_ocean_fn);
|
let water_alt = fill_sinks(map_size_lg, water_height_initial, is_ocean_fn);
|
||||||
/* let water_alt = (0..WORLD_SIZE.x * WORLD_SIZE.y)
|
/* let water_alt = (0..map_size_lg.chunks_len())
|
||||||
.into_par_iter()
|
.into_par_iter()
|
||||||
.map(|posi| water_height_initial(posi))
|
.map(|posi| water_height_initial(posi))
|
||||||
.collect::<Vec<_>>(); */
|
.collect::<Vec<_>>(); */
|
||||||
|
|
||||||
let rivers = get_rivers(&water_alt_pos, &water_alt, &dh, &indirection, &flux_rivers);
|
let rivers = get_rivers(
|
||||||
|
map_size_lg,
|
||||||
|
&water_alt_pos,
|
||||||
|
&water_alt,
|
||||||
|
&dh,
|
||||||
|
&indirection,
|
||||||
|
&flux_rivers,
|
||||||
|
);
|
||||||
|
|
||||||
let water_alt = indirection
|
let water_alt = indirection
|
||||||
.par_iter()
|
.par_iter()
|
||||||
@ -1198,11 +1232,15 @@ impl WorldSim {
|
|||||||
// Check whether any tiles around this tile are not water (since Lerp will
|
// Check whether any tiles around this tile are not water (since Lerp will
|
||||||
// ensure that they are included).
|
// ensure that they are included).
|
||||||
let pure_water = |posi: usize| {
|
let pure_water = |posi: usize| {
|
||||||
let pos = uniform_idx_as_vec2(posi);
|
let pos = uniform_idx_as_vec2(map_size_lg, posi);
|
||||||
for x in pos.x - 1..(pos.x + 1) + 1 {
|
for x in pos.x - 1..(pos.x + 1) + 1 {
|
||||||
for y in pos.y - 1..(pos.y + 1) + 1 {
|
for y in pos.y - 1..(pos.y + 1) + 1 {
|
||||||
if x >= 0 && y >= 0 && x < WORLD_SIZE.x as i32 && y < WORLD_SIZE.y as i32 {
|
if x >= 0
|
||||||
let posi = vec2_as_uniform_idx(Vec2::new(x, y));
|
&& y >= 0
|
||||||
|
&& x < map_size_lg.chunks().x as i32
|
||||||
|
&& y < map_size_lg.chunks().y as i32
|
||||||
|
{
|
||||||
|
let posi = vec2_as_uniform_idx(map_size_lg, Vec2::new(x, y));
|
||||||
if !is_underwater(posi) {
|
if !is_underwater(posi) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -1217,7 +1255,7 @@ impl WorldSim {
|
|||||||
|| {
|
|| {
|
||||||
rayon::join(
|
rayon::join(
|
||||||
|| {
|
|| {
|
||||||
uniform_noise(|posi, _| {
|
uniform_noise(map_size_lg, |posi, _| {
|
||||||
if pure_water(posi) {
|
if pure_water(posi) {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
@ -1228,7 +1266,7 @@ impl WorldSim {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
|| {
|
|| {
|
||||||
uniform_noise(|posi, _| {
|
uniform_noise(map_size_lg, |posi, _| {
|
||||||
if pure_water(posi) {
|
if pure_water(posi) {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
@ -1241,7 +1279,7 @@ impl WorldSim {
|
|||||||
|| {
|
|| {
|
||||||
rayon::join(
|
rayon::join(
|
||||||
|| {
|
|| {
|
||||||
uniform_noise(|posi, wposf| {
|
uniform_noise(map_size_lg, |posi, wposf| {
|
||||||
if pure_water(posi) {
|
if pure_water(posi) {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
@ -1251,7 +1289,7 @@ impl WorldSim {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
|| {
|
|| {
|
||||||
uniform_noise(|posi, wposf| {
|
uniform_noise(map_size_lg, |posi, wposf| {
|
||||||
// Check whether any tiles around this tile are water.
|
// Check whether any tiles around this tile are water.
|
||||||
if pure_water(posi) {
|
if pure_water(posi) {
|
||||||
None
|
None
|
||||||
@ -1283,13 +1321,14 @@ impl WorldSim {
|
|||||||
rivers,
|
rivers,
|
||||||
};
|
};
|
||||||
|
|
||||||
let chunks = (0..WORLD_SIZE.x * WORLD_SIZE.y)
|
let chunks = (0..map_size_lg.chunks_len())
|
||||||
.into_par_iter()
|
.into_par_iter()
|
||||||
.map(|i| SimChunk::generate(i, &gen_ctx, &gen_cdf))
|
.map(|i| SimChunk::generate(map_size_lg, i, &gen_ctx, &gen_cdf))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let mut this = Self {
|
let mut this = Self {
|
||||||
seed,
|
seed,
|
||||||
|
map_size_lg,
|
||||||
max_height: maxh as f32,
|
max_height: maxh as f32,
|
||||||
chunks,
|
chunks,
|
||||||
locations: Vec::new(),
|
locations: Vec::new(),
|
||||||
@ -1304,13 +1343,18 @@ impl WorldSim {
|
|||||||
this
|
this
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_size(&self) -> Vec2<u32> { WORLD_SIZE.map(|e| e as u32) }
|
#[inline(always)]
|
||||||
|
pub const fn map_size_lg(&self) -> MapSizeLg { self.map_size_lg }
|
||||||
|
|
||||||
|
pub fn get_size(&self) -> Vec2<u32> { self.map_size_lg().chunks().map(u32::from) }
|
||||||
|
|
||||||
/// Draw a map of the world based on chunk information. Returns a buffer of
|
/// Draw a map of the world based on chunk information. Returns a buffer of
|
||||||
/// u32s.
|
/// u32s.
|
||||||
pub fn get_map(&self) -> WorldMapMsg {
|
pub fn get_map(&self) -> WorldMapMsg {
|
||||||
let mut map_config = MapConfig::default();
|
let mut map_config = MapConfig::orthographic(
|
||||||
map_config.lgain = 1.0;
|
DEFAULT_WORLD_CHUNKS_LG,
|
||||||
|
core::ops::RangeInclusive::new(CONFIG.sea_level, CONFIG.sea_level + self.max_height),
|
||||||
|
);
|
||||||
// Build a horizon map.
|
// Build a horizon map.
|
||||||
let scale_angle = |angle: Alt| {
|
let scale_angle = |angle: Alt| {
|
||||||
(/* 0.0.max( */angle /* ) */
|
(/* 0.0.max( */angle /* ) */
|
||||||
@ -1325,14 +1369,14 @@ impl WorldSim {
|
|||||||
|
|
||||||
let samples_data = {
|
let samples_data = {
|
||||||
let column_sample = ColumnGen::new(self);
|
let column_sample = ColumnGen::new(self);
|
||||||
(0..WORLD_SIZE.product())
|
(0..self.map_size_lg().chunks_len())
|
||||||
.into_par_iter()
|
.into_par_iter()
|
||||||
.map_init(
|
.map_init(
|
||||||
|| Box::new(BlockGen::new(ColumnGen::new(self))),
|
|| Box::new(BlockGen::new(ColumnGen::new(self))),
|
||||||
|block_gen, posi| {
|
|block_gen, posi| {
|
||||||
let wpos = uniform_idx_as_vec2(posi);
|
let wpos = uniform_idx_as_vec2(self.map_size_lg(), posi);
|
||||||
let mut sample = column_sample.get(
|
let mut sample = column_sample.get(
|
||||||
uniform_idx_as_vec2(posi) * TerrainChunkSize::RECT_SIZE.map(|e| e as i32),
|
uniform_idx_as_vec2(self.map_size_lg(), posi) * TerrainChunkSize::RECT_SIZE.map(|e| e as i32),
|
||||||
)?;
|
)?;
|
||||||
let alt = sample.alt;
|
let alt = sample.alt;
|
||||||
/* let z_cache = block_gen.get_z_cache(wpos);
|
/* let z_cache = block_gen.get_z_cache(wpos);
|
||||||
@ -1353,7 +1397,7 @@ impl WorldSim {
|
|||||||
)
|
)
|
||||||
/* .map(|posi| {
|
/* .map(|posi| {
|
||||||
let mut sample = column_sample.get(
|
let mut sample = column_sample.get(
|
||||||
uniform_idx_as_vec2(posi) * TerrainChunkSize::RECT_SIZE.map(|e| e as i32),
|
uniform_idx_as_vec2(self.map_size_lg(), posi) * TerrainChunkSize::RECT_SIZE.map(|e| e as i32),
|
||||||
);
|
);
|
||||||
}) */
|
}) */
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
@ -1361,55 +1405,53 @@ impl WorldSim {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let horizons = get_horizon_map(
|
let horizons = get_horizon_map(
|
||||||
map_config.lgain,
|
self.map_size_lg(),
|
||||||
Aabr {
|
Aabr {
|
||||||
min: Vec2::zero(),
|
min: Vec2::zero(),
|
||||||
max: WORLD_SIZE.map(|e| e as i32),
|
max: self.map_size_lg().chunks().map(|e| e as i32),
|
||||||
},
|
},
|
||||||
CONFIG.sea_level as Alt,
|
CONFIG.sea_level,
|
||||||
(CONFIG.sea_level + self.max_height) as Alt,
|
CONFIG.sea_level + self.max_height,
|
||||||
|posi| {
|
|posi| {
|
||||||
/* let chunk = &self.chunks[posi];
|
/* let chunk = &self.chunks[posi];
|
||||||
chunk.alt.max(chunk.water_alt) as Alt */
|
chunk.alt.max(chunk.water_alt) as Alt */
|
||||||
let sample = samples_data[posi].as_ref();
|
let sample = samples_data[posi].as_ref();
|
||||||
sample
|
sample
|
||||||
.map(|s| s.alt.max(s.water_level))
|
.map(|s| s.alt.max(s.water_level))
|
||||||
.unwrap_or(CONFIG.sea_level) as Alt
|
.unwrap_or(CONFIG.sea_level)
|
||||||
},
|
},
|
||||||
|a| scale_angle(a),
|
|a| scale_angle(a.into()),
|
||||||
|h| scale_height(h),
|
|h| scale_height(h.into()),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let mut v = vec![0u32; WORLD_SIZE.x * WORLD_SIZE.y];
|
let mut v = vec![0u32; self.map_size_lg().chunks_len()];
|
||||||
let mut alts = vec![0u32; WORLD_SIZE.x * WORLD_SIZE.y];
|
let mut alts = vec![0u32; self.map_size_lg().chunks_len()];
|
||||||
// TODO: Parallelize again.
|
// TODO: Parallelize again.
|
||||||
let config = MapConfig {
|
map_config.is_shaded = false;
|
||||||
gain: self.max_height,
|
|
||||||
samples: Some(&samples_data),
|
|
||||||
is_shaded: false,
|
|
||||||
..map_config
|
|
||||||
};
|
|
||||||
|
|
||||||
config.generate(
|
map_config.generate(
|
||||||
|pos| config.sample_pos(self, pos),
|
|pos| sample_pos(&map_config, self, Some(&samples_data), pos),
|
||||||
|pos| config.sample_wpos(self, pos),
|
|pos| sample_wpos(&map_config, self, pos),
|
||||||
|pos, (r, g, b, _a)| {
|
|pos, (r, g, b, _a)| {
|
||||||
// We currently ignore alpha and replace it with the height at pos, scaled to
|
// We currently ignore alpha and replace it with the height at pos, scaled to
|
||||||
// u8.
|
// u8.
|
||||||
let alt = config.sample_wpos(
|
let alt = sample_wpos(
|
||||||
|
&map_config,
|
||||||
self,
|
self,
|
||||||
pos.map(|e| e as i32) * TerrainChunkSize::RECT_SIZE.map(|e| e as i32),
|
pos.map(|e| e as i32) * TerrainChunkSize::RECT_SIZE.map(|e| e as i32),
|
||||||
);
|
);
|
||||||
let a = 0; //(alt.min(1.0).max(0.0) * 255.0) as u8;
|
let a = 0; //(alt.min(1.0).max(0.0) * 255.0) as u8;
|
||||||
|
|
||||||
let posi = pos.y * WORLD_SIZE.x + pos.x;
|
// NOTE: Safe by invariants on map_size_lg.
|
||||||
|
let posi = (pos.y << self.map_size_lg().vec().x) | pos.x;
|
||||||
v[posi] = u32::from_le_bytes([r, g, b, a]);
|
v[posi] = u32::from_le_bytes([r, g, b, a]);
|
||||||
alts[posi] = (((alt.min(1.0).max(0.0) * 8191.0) as u32) & 0x1FFF) << 3;
|
alts[posi] = (((alt.min(1.0).max(0.0) * 8191.0) as u32) & 0x1FFF) << 3;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
WorldMapMsg {
|
WorldMapMsg {
|
||||||
dimensions: WORLD_SIZE.map(|e| e as u16),
|
dimensions_lg: self.map_size_lg().vec(),
|
||||||
|
sea_level: CONFIG.sea_level,
|
||||||
max_height: self.max_height,
|
max_height: self.max_height,
|
||||||
rgba: v,
|
rgba: v,
|
||||||
alt: alts,
|
alt: alts,
|
||||||
@ -1422,7 +1464,7 @@ impl WorldSim {
|
|||||||
let mut rng = self.rng.clone();
|
let mut rng = self.rng.clone();
|
||||||
|
|
||||||
let cell_size = 16;
|
let cell_size = 16;
|
||||||
let grid_size = WORLD_SIZE / cell_size;
|
let grid_size = self.map_size_lg().chunks().map(usize::from) / cell_size;
|
||||||
let loc_count = 100;
|
let loc_count = 100;
|
||||||
|
|
||||||
let mut loc_grid = vec![None; grid_size.product()];
|
let mut loc_grid = vec![None; grid_size.product()];
|
||||||
@ -1490,7 +1532,7 @@ impl WorldSim {
|
|||||||
.par_iter_mut()
|
.par_iter_mut()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.for_each(|(ij, chunk)| {
|
.for_each(|(ij, chunk)| {
|
||||||
let chunk_pos = uniform_idx_as_vec2(ij);
|
let chunk_pos = uniform_idx_as_vec2(self.map_size_lg(), ij);
|
||||||
let i = chunk_pos.x as usize;
|
let i = chunk_pos.x as usize;
|
||||||
let j = chunk_pos.y as usize;
|
let j = chunk_pos.y as usize;
|
||||||
let block_pos = Vec2::new(
|
let block_pos = Vec2::new(
|
||||||
@ -1530,10 +1572,10 @@ impl WorldSim {
|
|||||||
// Create waypoints
|
// Create waypoints
|
||||||
const WAYPOINT_EVERY: usize = 16;
|
const WAYPOINT_EVERY: usize = 16;
|
||||||
let this = &self;
|
let this = &self;
|
||||||
let waypoints = (0..WORLD_SIZE.x)
|
let waypoints = (0..this.map_size_lg().chunks().x)
|
||||||
.step_by(WAYPOINT_EVERY)
|
.step_by(WAYPOINT_EVERY)
|
||||||
.map(|i| {
|
.map(|i| {
|
||||||
(0..WORLD_SIZE.y)
|
(0..this.map_size_lg().chunks().y)
|
||||||
.step_by(WAYPOINT_EVERY)
|
.step_by(WAYPOINT_EVERY)
|
||||||
.map(move |j| (i, j))
|
.map(move |j| (i, j))
|
||||||
})
|
})
|
||||||
@ -1576,10 +1618,10 @@ impl WorldSim {
|
|||||||
|
|
||||||
pub fn get(&self, chunk_pos: Vec2<i32>) -> Option<&SimChunk> {
|
pub fn get(&self, chunk_pos: Vec2<i32>) -> Option<&SimChunk> {
|
||||||
if chunk_pos
|
if chunk_pos
|
||||||
.map2(WORLD_SIZE, |e, sz| e >= 0 && e < sz as i32)
|
.map2(self.map_size_lg().chunks(), |e, sz| e >= 0 && e < sz as i32)
|
||||||
.reduce_and()
|
.reduce_and()
|
||||||
{
|
{
|
||||||
Some(&self.chunks[vec2_as_uniform_idx(chunk_pos)])
|
Some(&self.chunks[vec2_as_uniform_idx(self.map_size_lg(), chunk_pos)])
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@ -1607,11 +1649,12 @@ impl WorldSim {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_mut(&mut self, chunk_pos: Vec2<i32>) -> Option<&mut SimChunk> {
|
pub fn get_mut(&mut self, chunk_pos: Vec2<i32>) -> Option<&mut SimChunk> {
|
||||||
|
let map_size_lg = self.map_size_lg();
|
||||||
if chunk_pos
|
if chunk_pos
|
||||||
.map2(WORLD_SIZE, |e, sz| e >= 0 && e < sz as i32)
|
.map2(map_size_lg.chunks(), |e, sz| e >= 0 && e < sz as i32)
|
||||||
.reduce_and()
|
.reduce_and()
|
||||||
{
|
{
|
||||||
Some(&mut self.chunks[vec2_as_uniform_idx(chunk_pos)])
|
Some(&mut self.chunks[vec2_as_uniform_idx(map_size_lg, chunk_pos)])
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@ -1619,16 +1662,18 @@ impl WorldSim {
|
|||||||
|
|
||||||
pub fn get_base_z(&self, chunk_pos: Vec2<i32>) -> Option<f32> {
|
pub fn get_base_z(&self, chunk_pos: Vec2<i32>) -> Option<f32> {
|
||||||
if !chunk_pos
|
if !chunk_pos
|
||||||
.map2(WORLD_SIZE, |e, sz| e > 0 && e < sz as i32 - 2)
|
.map2(self.map_size_lg().chunks(), |e, sz| {
|
||||||
|
e > 0 && e < sz as i32 - 2
|
||||||
|
})
|
||||||
.reduce_and()
|
.reduce_and()
|
||||||
{
|
{
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let chunk_idx = vec2_as_uniform_idx(chunk_pos);
|
let chunk_idx = vec2_as_uniform_idx(self.map_size_lg(), chunk_pos);
|
||||||
local_cells(chunk_idx)
|
local_cells(self.map_size_lg(), chunk_idx)
|
||||||
.flat_map(|neighbor_idx| {
|
.flat_map(|neighbor_idx| {
|
||||||
let neighbor_pos = uniform_idx_as_vec2(neighbor_idx);
|
let neighbor_pos = uniform_idx_as_vec2(self.map_size_lg(), neighbor_idx);
|
||||||
let neighbor_chunk = self.get(neighbor_pos);
|
let neighbor_chunk = self.get(neighbor_pos);
|
||||||
let river_kind = neighbor_chunk.and_then(|c| c.river.river_kind);
|
let river_kind = neighbor_chunk.and_then(|c| c.river.river_kind);
|
||||||
let has_water = river_kind.is_some() && river_kind != Some(RiverKind::Ocean);
|
let has_water = river_kind.is_some() && river_kind != Some(RiverKind::Ocean);
|
||||||
@ -1904,11 +1949,10 @@ pub struct RegionInfo {
|
|||||||
impl SimChunk {
|
impl SimChunk {
|
||||||
#[allow(clippy::if_same_then_else)] // TODO: Pending review in #587
|
#[allow(clippy::if_same_then_else)] // TODO: Pending review in #587
|
||||||
#[allow(clippy::unnested_or_patterns)] // TODO: Pending review in #587
|
#[allow(clippy::unnested_or_patterns)] // TODO: Pending review in #587
|
||||||
fn generate(posi: usize, gen_ctx: &GenCtx, gen_cdf: &GenCdf) -> Self {
|
fn generate(map_size_lg: MapSizeLg, posi: usize, gen_ctx: &GenCtx, gen_cdf: &GenCdf) -> Self {
|
||||||
let pos = uniform_idx_as_vec2(posi);
|
let pos = uniform_idx_as_vec2(map_size_lg, posi);
|
||||||
let wposf = (pos * TerrainChunkSize::RECT_SIZE.map(|e| e as i32)).map(|e| e as f64);
|
let wposf = (pos * TerrainChunkSize::RECT_SIZE.map(|e| e as i32)).map(|e| e as f64);
|
||||||
|
|
||||||
let _map_edge_factor = map_edge_factor(posi);
|
|
||||||
let (_, chaos) = gen_cdf.chaos[posi];
|
let (_, chaos) = gen_cdf.chaos[posi];
|
||||||
let alt_pre = gen_cdf.alt[posi] as f32;
|
let alt_pre = gen_cdf.alt[posi] as f32;
|
||||||
let basement_pre = gen_cdf.basement[posi] as f32;
|
let basement_pre = gen_cdf.basement[posi] as f32;
|
||||||
@ -1929,7 +1973,7 @@ impl SimChunk {
|
|||||||
// can always add a small x component).
|
// can always add a small x component).
|
||||||
//
|
//
|
||||||
// Not clear that we want this yet, let's see.
|
// Not clear that we want this yet, let's see.
|
||||||
let latitude_uniform = (pos.y as f32 / WORLD_SIZE.y as f32).sub(0.5).mul(2.0);
|
let latitude_uniform = (pos.y as f32 / f32::from(self.map_size_lg().chunks().y)).sub(0.5).mul(2.0);
|
||||||
|
|
||||||
// Even less granular--if this matters we can make the sign affect the quantiy slightly.
|
// Even less granular--if this matters we can make the sign affect the quantiy slightly.
|
||||||
let abs_lat_uniform = latitude_uniform.abs(); */
|
let abs_lat_uniform = latitude_uniform.abs(); */
|
||||||
@ -1962,7 +2006,7 @@ impl SimChunk {
|
|||||||
panic!("Uh... shouldn't this never, ever happen?");
|
panic!("Uh... shouldn't this never, ever happen?");
|
||||||
} else {
|
} else {
|
||||||
Some(
|
Some(
|
||||||
uniform_idx_as_vec2(downhill_pre as usize)
|
uniform_idx_as_vec2(map_size_lg, downhill_pre as usize)
|
||||||
* TerrainChunkSize::RECT_SIZE.map(|e| e as i32),
|
* TerrainChunkSize::RECT_SIZE.map(|e| e as i32),
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
use super::WORLD_SIZE;
|
|
||||||
use bitvec::prelude::{bitbox, BitBox};
|
use bitvec::prelude::{bitbox, BitBox};
|
||||||
use common::{terrain::TerrainChunkSize, vol::RectVolSize};
|
use common::{
|
||||||
|
terrain::{neighbors, uniform_idx_as_vec2, vec2_as_uniform_idx, MapSizeLg, TerrainChunkSize},
|
||||||
|
vol::RectVolSize,
|
||||||
|
};
|
||||||
use noise::{MultiFractal, NoiseFn, Perlin, Point2, Point3, Point4, Seedable};
|
use noise::{MultiFractal, NoiseFn, Perlin, Point2, Point3, Point4, Seedable};
|
||||||
use num::Float;
|
use num::Float;
|
||||||
use rayon::prelude::*;
|
use rayon::prelude::*;
|
||||||
@ -8,14 +10,15 @@ use std::{f32, f64, ops::Mul, u32};
|
|||||||
use vek::*;
|
use vek::*;
|
||||||
|
|
||||||
/// Calculates the smallest distance along an axis (x, y) from an edge of
|
/// Calculates the smallest distance along an axis (x, y) from an edge of
|
||||||
/// the world. This value is maximal at WORLD_SIZE / 2 and minimized at the
|
/// the world. This value is maximal at map_size_lg.chunks() / 2 and
|
||||||
/// extremes (0 or WORLD_SIZE on one or more axes). It then divides the
|
/// minimized at the
|
||||||
/// quantity by cell_size, so the final result is 1 when we are not in a cell
|
/// extremes (0 or map_size_lg.chunks() on one or more axes). It then divides
|
||||||
/// along the edge of the world, and ranges between 0 and 1 otherwise (lower
|
/// the quantity by cell_size, so the final result is 1 when we are not in a
|
||||||
/// when the chunk is closer to the edge).
|
/// cell along the edge of the world, and ranges between 0 and 1 otherwise
|
||||||
pub fn map_edge_factor(posi: usize) -> f32 {
|
/// (lower when the chunk is closer to the edge).
|
||||||
uniform_idx_as_vec2(posi)
|
pub fn map_edge_factor(map_size_lg: MapSizeLg, posi: usize) -> f32 {
|
||||||
.map2(WORLD_SIZE.map(|e| e as i32), |e, sz| {
|
uniform_idx_as_vec2(map_size_lg, posi)
|
||||||
|
.map2(map_size_lg.chunks().map(i32::from), |e, sz| {
|
||||||
(sz / 2 - (e - sz / 2).abs()) as f32 / (16.0 / 1024.0 * sz as f32)
|
(sz / 2 - (e - sz / 2).abs()) as f32 / (16.0 / 1024.0 * sz as f32)
|
||||||
})
|
})
|
||||||
.reduce_partial_min()
|
.reduce_partial_min()
|
||||||
@ -118,8 +121,6 @@ pub fn cdf_irwin_hall<const N: usize>(weights: &[f32; N], samples: [f32; N]) ->
|
|||||||
/// returned by the noise function applied to every chunk in the game). Second
|
/// returned by the noise function applied to every chunk in the game). Second
|
||||||
/// component is the cached value of the noise function that generated the
|
/// component is the cached value of the noise function that generated the
|
||||||
/// index.
|
/// index.
|
||||||
///
|
|
||||||
/// NOTE: Length should always be WORLD_SIZE.x * WORLD_SIZE.y.
|
|
||||||
pub type InverseCdf<F = f32> = Box<[(f32, F)]>;
|
pub type InverseCdf<F = f32> = Box<[(f32, F)]>;
|
||||||
|
|
||||||
/// NOTE: First component is estimated horizon angles at each chunk; second
|
/// NOTE: First component is estimated horizon angles at each chunk; second
|
||||||
@ -127,19 +128,6 @@ pub type InverseCdf<F = f32> = Box<[(f32, F)]>;
|
|||||||
/// for making shadows volumetric).
|
/// for making shadows volumetric).
|
||||||
pub type HorizonMap<A, H> = (Vec<A>, Vec<H>);
|
pub type HorizonMap<A, H> = (Vec<A>, Vec<H>);
|
||||||
|
|
||||||
/// Computes the position Vec2 of a SimChunk from an index, where the index was
|
|
||||||
/// generated by uniform_noise.
|
|
||||||
pub fn uniform_idx_as_vec2(idx: usize) -> Vec2<i32> {
|
|
||||||
Vec2::new((idx % WORLD_SIZE.x) as i32, (idx / WORLD_SIZE.x) as i32)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Computes the index of a Vec2 of a SimChunk from a position, where the index
|
|
||||||
/// is generated by uniform_noise. NOTE: Both components of idx should be
|
|
||||||
/// in-bounds!
|
|
||||||
pub fn vec2_as_uniform_idx(idx: Vec2<i32>) -> usize {
|
|
||||||
(idx.y as usize * WORLD_SIZE.x + idx.x as usize) as usize
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Compute inverse cumulative distribution function for arbitrary function f,
|
/// Compute inverse cumulative distribution function for arbitrary function f,
|
||||||
/// the hard way. We pre-generate noise values prior to worldgen, then sort
|
/// the hard way. We pre-generate noise values prior to worldgen, then sort
|
||||||
/// them in order to determine the correct position in the sorted order. That
|
/// them in order to determine the correct position in the sorted order. That
|
||||||
@ -178,15 +166,17 @@ pub fn vec2_as_uniform_idx(idx: Vec2<i32>) -> usize {
|
|||||||
/// value actually uses the same one we were using here easier). Also returns
|
/// value actually uses the same one we were using here easier). Also returns
|
||||||
/// the "inverted index" pointing from a position to a noise.
|
/// the "inverted index" pointing from a position to a noise.
|
||||||
pub fn uniform_noise<F: Float + Send>(
|
pub fn uniform_noise<F: Float + Send>(
|
||||||
|
map_size_lg: MapSizeLg,
|
||||||
f: impl Fn(usize, Vec2<f64>) -> Option<F> + Sync,
|
f: impl Fn(usize, Vec2<f64>) -> Option<F> + Sync,
|
||||||
) -> (InverseCdf<F>, Box<[(usize, F)]>) {
|
) -> (InverseCdf<F>, Box<[(usize, F)]>) {
|
||||||
let mut noise = (0..WORLD_SIZE.x * WORLD_SIZE.y)
|
let mut noise = (0..map_size_lg.chunks_len())
|
||||||
.into_par_iter()
|
.into_par_iter()
|
||||||
.filter_map(|i| {
|
.filter_map(|i| {
|
||||||
f(
|
f(
|
||||||
i,
|
i,
|
||||||
(uniform_idx_as_vec2(i) * TerrainChunkSize::RECT_SIZE.map(|e| e as i32))
|
(uniform_idx_as_vec2(map_size_lg, i)
|
||||||
.map(|e| e as f64),
|
* TerrainChunkSize::RECT_SIZE.map(|e| e as i32))
|
||||||
|
.map(|e| e as f64),
|
||||||
)
|
)
|
||||||
.map(|res| (i, res))
|
.map(|res| (i, res))
|
||||||
})
|
})
|
||||||
@ -202,7 +192,7 @@ pub fn uniform_noise<F: Float + Send>(
|
|||||||
// position of the noise in the sorted vector (divided by the vector length).
|
// position of the noise in the sorted vector (divided by the vector length).
|
||||||
// This guarantees a uniform distribution among the samples (excluding those
|
// This guarantees a uniform distribution among the samples (excluding those
|
||||||
// that returned None, which will remain at zero).
|
// that returned None, which will remain at zero).
|
||||||
let mut uniform_noise = vec![(0.0, F::nan()); WORLD_SIZE.x * WORLD_SIZE.y].into_boxed_slice();
|
let mut uniform_noise = vec![(0.0, F::nan()); map_size_lg.chunks_len()].into_boxed_slice();
|
||||||
// NOTE: Consider using try_into here and elsewhere in this function, since
|
// NOTE: Consider using try_into here and elsewhere in this function, since
|
||||||
// i32::MAX technically doesn't fit in an f32 (even if we should never reach
|
// i32::MAX technically doesn't fit in an f32 (even if we should never reach
|
||||||
// that limit).
|
// that limit).
|
||||||
@ -223,8 +213,8 @@ pub fn uniform_noise<F: Float + Send>(
|
|||||||
/// its top-right/down-right/down neighbors, the twelve chunks surrounding this
|
/// its top-right/down-right/down neighbors, the twelve chunks surrounding this
|
||||||
/// box (its "perimeter") are also inspected.
|
/// box (its "perimeter") are also inspected.
|
||||||
#[allow(clippy::useless_conversion)] // TODO: Pending review in #587
|
#[allow(clippy::useless_conversion)] // TODO: Pending review in #587
|
||||||
pub fn local_cells(posi: usize) -> impl Clone + Iterator<Item = usize> {
|
pub fn local_cells(map_size_lg: MapSizeLg, posi: usize) -> impl Clone + Iterator<Item = usize> {
|
||||||
let pos = uniform_idx_as_vec2(posi);
|
let pos = uniform_idx_as_vec2(map_size_lg, posi);
|
||||||
// NOTE: want to keep this such that the chunk index is in ascending order!
|
// NOTE: want to keep this such that the chunk index is in ascending order!
|
||||||
let grid_size = 3i32;
|
let grid_size = 3i32;
|
||||||
let grid_bounds = 2 * grid_size + 1;
|
let grid_bounds = 2 * grid_size + 1;
|
||||||
@ -236,51 +226,35 @@ pub fn local_cells(posi: usize) -> impl Clone + Iterator<Item = usize> {
|
|||||||
pos.y + (index / grid_bounds) - grid_size,
|
pos.y + (index / grid_bounds) - grid_size,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.filter(|pos| {
|
.filter(move |pos| {
|
||||||
pos.x >= 0 && pos.y >= 0 && pos.x < WORLD_SIZE.x as i32 && pos.y < WORLD_SIZE.y as i32
|
pos.x >= 0
|
||||||
|
&& pos.y >= 0
|
||||||
|
&& pos.x < map_size_lg.chunks().x as i32
|
||||||
|
&& pos.y < map_size_lg.chunks().y as i32
|
||||||
})
|
})
|
||||||
.map(vec2_as_uniform_idx)
|
.map(move |e| vec2_as_uniform_idx(map_size_lg, e))
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE: want to keep this such that the chunk index is in ascending order!
|
|
||||||
pub const NEIGHBOR_DELTA: [(i32, i32); 8] = [
|
|
||||||
(-1, -1),
|
|
||||||
(0, -1),
|
|
||||||
(1, -1),
|
|
||||||
(-1, 0),
|
|
||||||
(1, 0),
|
|
||||||
(-1, 1),
|
|
||||||
(0, 1),
|
|
||||||
(1, 1),
|
|
||||||
];
|
|
||||||
|
|
||||||
/// Iterate through all cells adjacent to a chunk.
|
|
||||||
pub fn neighbors(posi: usize) -> impl Clone + Iterator<Item = usize> {
|
|
||||||
let pos = uniform_idx_as_vec2(posi);
|
|
||||||
NEIGHBOR_DELTA
|
|
||||||
.iter()
|
|
||||||
.map(move |&(x, y)| Vec2::new(pos.x + x, pos.y + y))
|
|
||||||
.filter(|pos| {
|
|
||||||
pos.x >= 0 && pos.y >= 0 && pos.x < WORLD_SIZE.x as i32 && pos.y < WORLD_SIZE.y as i32
|
|
||||||
})
|
|
||||||
.map(vec2_as_uniform_idx)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note that we should already have okay cache locality since we have a grid.
|
// Note that we should already have okay cache locality since we have a grid.
|
||||||
pub fn uphill<'a>(dh: &'a [isize], posi: usize) -> impl Clone + Iterator<Item = usize> + 'a {
|
pub fn uphill<'a>(
|
||||||
neighbors(posi).filter(move |&posj| dh[posj] == posi as isize)
|
map_size_lg: MapSizeLg,
|
||||||
|
dh: &'a [isize],
|
||||||
|
posi: usize,
|
||||||
|
) -> impl Clone + Iterator<Item = usize> + 'a {
|
||||||
|
neighbors(map_size_lg, posi).filter(move |&posj| dh[posj] == posi as isize)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compute the neighbor "most downhill" from all chunks.
|
/// Compute the neighbor "most downhill" from all chunks.
|
||||||
///
|
///
|
||||||
/// TODO: See if allocating in advance is worthwhile.
|
/// TODO: See if allocating in advance is worthwhile.
|
||||||
pub fn downhill<F: Float>(
|
pub fn downhill<F: Float>(
|
||||||
|
map_size_lg: MapSizeLg,
|
||||||
h: impl Fn(usize) -> F + Sync,
|
h: impl Fn(usize) -> F + Sync,
|
||||||
is_ocean: impl Fn(usize) -> bool + Sync,
|
is_ocean: impl Fn(usize) -> bool + Sync,
|
||||||
) -> Box<[isize]> {
|
) -> Box<[isize]> {
|
||||||
// Constructs not only the list of downhill nodes, but also computes an ordering
|
// Constructs not only the list of downhill nodes, but also computes an ordering
|
||||||
// (visiting nodes in order from roots to leaves).
|
// (visiting nodes in order from roots to leaves).
|
||||||
(0..WORLD_SIZE.x * WORLD_SIZE.y)
|
(0..map_size_lg.chunks_len())
|
||||||
.into_par_iter()
|
.into_par_iter()
|
||||||
.map(|posi| {
|
.map(|posi| {
|
||||||
let nh = h(posi);
|
let nh = h(posi);
|
||||||
@ -289,7 +263,7 @@ pub fn downhill<F: Float>(
|
|||||||
} else {
|
} else {
|
||||||
let mut best = -1;
|
let mut best = -1;
|
||||||
let mut besth = nh;
|
let mut besth = nh;
|
||||||
for nposi in neighbors(posi) {
|
for nposi in neighbors(map_size_lg, posi) {
|
||||||
let nbh = h(nposi);
|
let nbh = h(nposi);
|
||||||
if nbh < besth {
|
if nbh < besth {
|
||||||
besth = nbh;
|
besth = nbh;
|
||||||
@ -354,37 +328,37 @@ fn get_interpolated_bilinear<T, F>(&self, pos: Vec2<i32>, mut f: F) -> Option<T>
|
|||||||
/// - posi is at the side of the world (map_edge_factor(posi) == 0.0)
|
/// - posi is at the side of the world (map_edge_factor(posi) == 0.0)
|
||||||
/// - posi has a neighboring ocean tile, and has a height below sea level
|
/// - posi has a neighboring ocean tile, and has a height below sea level
|
||||||
/// (oldh(posi) <= 0.0).
|
/// (oldh(posi) <= 0.0).
|
||||||
pub fn get_oceans<F: Float>(oldh: impl Fn(usize) -> F + Sync) -> BitBox {
|
pub fn get_oceans<F: Float>(map_size_lg: MapSizeLg, oldh: impl Fn(usize) -> F + Sync) -> BitBox {
|
||||||
// We can mark tiles as ocean candidates by scanning row by row, since the top
|
// We can mark tiles as ocean candidates by scanning row by row, since the top
|
||||||
// edge is ocean, the sides are connected to it, and any subsequent ocean
|
// edge is ocean, the sides are connected to it, and any subsequent ocean
|
||||||
// tiles must be connected to it.
|
// tiles must be connected to it.
|
||||||
let mut is_ocean = bitbox![0; WORLD_SIZE.x * WORLD_SIZE.y];
|
let mut is_ocean = bitbox![0; map_size_lg.chunks_len()];
|
||||||
let mut stack = Vec::new();
|
let mut stack = Vec::new();
|
||||||
let mut do_push = |pos| {
|
let mut do_push = |pos| {
|
||||||
let posi = vec2_as_uniform_idx(pos);
|
let posi = vec2_as_uniform_idx(map_size_lg, pos);
|
||||||
if oldh(posi) <= F::zero() {
|
if oldh(posi) <= F::zero() {
|
||||||
stack.push(posi);
|
stack.push(posi);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
for x in 0..WORLD_SIZE.x as i32 {
|
for x in 0..map_size_lg.chunks().x as i32 {
|
||||||
do_push(Vec2::new(x, 0));
|
do_push(Vec2::new(x, 0));
|
||||||
do_push(Vec2::new(x, WORLD_SIZE.y as i32 - 1));
|
do_push(Vec2::new(x, map_size_lg.chunks().y as i32 - 1));
|
||||||
}
|
}
|
||||||
for y in 1..WORLD_SIZE.y as i32 - 1 {
|
for y in 1..map_size_lg.chunks().y as i32 - 1 {
|
||||||
do_push(Vec2::new(0, y));
|
do_push(Vec2::new(0, y));
|
||||||
do_push(Vec2::new(WORLD_SIZE.x as i32 - 1, y));
|
do_push(Vec2::new(map_size_lg.chunks().x as i32 - 1, y));
|
||||||
}
|
}
|
||||||
while let Some(chunk_idx) = stack.pop() {
|
while let Some(chunk_idx) = stack.pop() {
|
||||||
// println!("Ocean chunk {:?}: {:?}", uniform_idx_as_vec2(chunk_idx),
|
// println!("Ocean chunk {:?}: {:?}", uniform_idx_as_vec2(map_size_lg,
|
||||||
// oldh(chunk_idx));
|
// chunk_idx), oldh(chunk_idx));
|
||||||
let mut is_ocean = is_ocean.get_mut(chunk_idx).unwrap();
|
let mut is_ocean = is_ocean.get_mut(chunk_idx).unwrap();
|
||||||
if *is_ocean {
|
if *is_ocean {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
*is_ocean = true;
|
*is_ocean = true;
|
||||||
stack.extend(neighbors(chunk_idx).filter(|&neighbor_idx| {
|
stack.extend(neighbors(map_size_lg, chunk_idx).filter(|&neighbor_idx| {
|
||||||
// println!("Ocean neighbor: {:?}: {:?}", uniform_idx_as_vec2(neighbor_idx),
|
// println!("Ocean neighbor: {:?}: {:?}", uniform_idx_as_vec2(map_size_lg,
|
||||||
// oldh(neighbor_idx));
|
// neighbor_idx), oldh(neighbor_idx));
|
||||||
oldh(neighbor_idx) <= F::zero()
|
oldh(neighbor_idx) <= F::zero()
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@ -393,7 +367,7 @@ pub fn get_oceans<F: Float>(oldh: impl Fn(usize) -> F + Sync) -> BitBox {
|
|||||||
|
|
||||||
/// Finds the horizon map for sunlight for the given chunks.
|
/// Finds the horizon map for sunlight for the given chunks.
|
||||||
pub fn get_horizon_map<F: Float + Sync, A: Send, H: Send>(
|
pub fn get_horizon_map<F: Float + Sync, A: Send, H: Send>(
|
||||||
lgain: F,
|
map_size_lg: MapSizeLg,
|
||||||
bounds: Aabr<i32>,
|
bounds: Aabr<i32>,
|
||||||
minh: F,
|
minh: F,
|
||||||
maxh: F,
|
maxh: F,
|
||||||
@ -416,7 +390,7 @@ pub fn get_horizon_map<F: Float + Sync, A: Send, H: Send>(
|
|||||||
};
|
};
|
||||||
// let epsilon = F::epsilon() * if let x = F::from(map_size.x) { x } else {
|
// let epsilon = F::epsilon() * if let x = F::from(map_size.x) { x } else {
|
||||||
// return Err(()) };
|
// return Err(()) };
|
||||||
let march = |dx: isize, maxdx: fn(isize) -> isize| {
|
let march = |dx: isize, maxdx: fn(isize, map_size_lg: MapSizeLg) -> isize| {
|
||||||
let mut angles = Vec::with_capacity(map_len);
|
let mut angles = Vec::with_capacity(map_len);
|
||||||
let mut heights = Vec::with_capacity(map_len);
|
let mut heights = Vec::with_capacity(map_len);
|
||||||
(0..map_len)
|
(0..map_len)
|
||||||
@ -425,14 +399,14 @@ pub fn get_horizon_map<F: Float + Sync, A: Send, H: Send>(
|
|||||||
let wposi =
|
let wposi =
|
||||||
bounds.min + Vec2::new((posi % map_size.x) as i32, (posi / map_size.x) as i32);
|
bounds.min + Vec2::new((posi % map_size.x) as i32, (posi / map_size.x) as i32);
|
||||||
if wposi.reduce_partial_min() < 0
|
if wposi.reduce_partial_min() < 0
|
||||||
|| wposi.x as usize >= WORLD_SIZE.x
|
|| wposi.x as usize >= usize::from(map_size_lg.chunks().x)
|
||||||
|| wposi.y as usize >= WORLD_SIZE.y
|
|| wposi.y as usize >= usize::from(map_size_lg.chunks().y)
|
||||||
{
|
{
|
||||||
return (to_angle(F::zero()), to_height(F::zero()));
|
return (to_angle(F::zero()), to_height(F::zero()));
|
||||||
}
|
}
|
||||||
let posi = vec2_as_uniform_idx(wposi);
|
let posi = vec2_as_uniform_idx(map_size_lg, wposi);
|
||||||
// March in the given direction.
|
// March in the given direction.
|
||||||
let maxdx = maxdx(wposi.x as isize);
|
let maxdx = maxdx(wposi.x as isize, map_size_lg);
|
||||||
let mut slope = F::zero();
|
let mut slope = F::zero();
|
||||||
let h0 = h(posi);
|
let h0 = h(posi);
|
||||||
let h = if h0 < minh {
|
let h = if h0 < minh {
|
||||||
@ -458,14 +432,16 @@ pub fn get_horizon_map<F: Float + Sync, A: Send, H: Send>(
|
|||||||
}
|
}
|
||||||
h0 - minh + max_height
|
h0 - minh + max_height
|
||||||
};
|
};
|
||||||
let a = slope * lgain;
|
let a = slope;
|
||||||
(to_angle(a), to_height(h))
|
(to_angle(a), to_height(h))
|
||||||
})
|
})
|
||||||
.unzip_into_vecs(&mut angles, &mut heights);
|
.unzip_into_vecs(&mut angles, &mut heights);
|
||||||
(angles, heights)
|
(angles, heights)
|
||||||
};
|
};
|
||||||
let west = march(-1, |x| x);
|
let west = march(-1, |x, _| x);
|
||||||
let east = march(1, |x| (WORLD_SIZE.x - x as usize) as isize);
|
let east = march(1, |x, map_size_lg| {
|
||||||
|
(usize::from(map_size_lg.chunks().x) - x as usize) as isize
|
||||||
|
});
|
||||||
Ok([west, east])
|
Ok([west, east])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user