Allow canceling chunk generation.

Currently we only do this when no players are in range of the chunk.  We
also send the first client who posted the chunk a message indicating
that it's canceled, the hope being that this will be a performance win
in single player mode since you don't have to wait three seconds to
realize that the server won't generate the chunk for you.

We now check an atomic flag for every column sample in a chunk.  We
could probably do this less frequently, but since it's a relaxed load it
has essentially no performance impact on Intel architectures.
This commit is contained in:
Joshua Yanovski 2019-10-16 11:39:41 +00:00 committed by Marcel
parent 6cd78dcb4c
commit 8ae2692b6e
25 changed files with 2953 additions and 427 deletions

84
Cargo.lock generated
View File

@ -263,6 +263,11 @@ name = "bitflags"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "bitvec"
version = "0.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "blake2b_simd"
version = "0.5.8"
@ -485,19 +490,21 @@ dependencies = [
[[package]]
name = "cocoa"
version = "0.14.0"
version = "0.18.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bitflags 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"block 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"core-graphics 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)",
"core-foundation 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)",
"core-graphics 0.17.3 (registry+https://github.com/rust-lang/crates.io-index)",
"foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
"objc 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "cocoa"
version = "0.18.4"
version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bitflags 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
@ -566,15 +573,6 @@ name = "constant_time_eq"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "core-foundation"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"core-foundation-sys 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "core-foundation"
version = "0.6.4"
@ -584,30 +582,11 @@ dependencies = [
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "core-foundation-sys"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "core-foundation-sys"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "core-graphics"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bitflags 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"core-foundation 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
"foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "core-graphics"
version = "0.17.3"
@ -1927,14 +1906,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "msgbox"
version = "0.2.0"
source = "git+https://github.com/bekker/msgbox-rs.git#d3e12e1cbfcd280bb4de5ad8032bded37d8e573f"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cocoa 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)",
"cocoa 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)",
"gtk 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
"objc 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
"user32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@ -2843,6 +2821,11 @@ dependencies = [
"serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "roots"
version = "0.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "rouille"
version = "3.0.0"
@ -3476,15 +3459,6 @@ dependencies = [
"percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "user32-sys"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "utf8-ranges"
version = "1.0.4"
@ -3532,7 +3506,9 @@ dependencies = [
name = "veloren-client"
version = "0.4.0"
dependencies = [
"byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"hashbrown 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
"image 0.22.2 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
"specs 0.14.3 (registry+https://github.com/rust-lang/crates.io-index)",
@ -3633,7 +3609,7 @@ dependencies = [
"image 0.22.2 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"msgbox 0.2.0 (git+https://github.com/bekker/msgbox-rs.git)",
"msgbox 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"num 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
"rodio 0.9.0 (git+https://github.com/RustAudio/rodio?rev=e5474a2)",
@ -3656,13 +3632,19 @@ name = "veloren-world"
version = "0.4.0"
dependencies = [
"arr_macro 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"bitvec 0.15.1 (registry+https://github.com/rust-lang/crates.io-index)",
"hashbrown 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"minifb 0.13.0 (git+https://github.com/emoon/rust_minifb.git)",
"noise 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
"num 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"ordered-float 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
"rand_chacha 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"rayon 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"ron 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
"roots 0.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)",
"vek 0.9.9 (registry+https://github.com/rust-lang/crates.io-index)",
"veloren-common 0.4.0",
@ -3888,6 +3870,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum bitflags 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1370e9fc2a6ae53aea8b7a5110edbd08836ed87c88736dfabccade1c2b44bff4"
"checksum bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4efd02e230a02e18f92fc2735f44597385ed02ad8f831e7c1c1156ee5e1ab3a5"
"checksum bitflags 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8a606a02debe2813760609f57a64a2ffd27d9fdf5b2f133eaca0b248dd92cdd2"
"checksum bitvec 0.15.1 (registry+https://github.com/rust-lang/crates.io-index)" = "461d7d0e952343f575470daeb04d38aad19675b4f170e122c6b5dd618612c8a8"
"checksum blake2b_simd 0.5.8 (registry+https://github.com/rust-lang/crates.io-index)" = "5850aeee1552f495dd0250014cf64b82b7c8879a89d83b33bbdace2cc4f63182"
"checksum block 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a"
"checksum brotli-sys 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4445dea95f4c2b41cde57cc9fee236ae4dbae88d8fcbdb4750fc1bb5d86aaecd"
@ -3915,8 +3898,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum claxon 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f86c952727a495bda7abaf09bafdee1a939194dd793d9a8e26281df55ac43b00"
"checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
"checksum cmake 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "81fb25b677f8bf1eb325017cb6bb8452f87969db0fedb4f757b297bee78a7c62"
"checksum cocoa 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b0c23085dde1ef4429df6e5896b89356d35cdd321fb43afe3e378d010bb5adc6"
"checksum cocoa 0.18.4 (registry+https://github.com/rust-lang/crates.io-index)" = "cf79daa4e11e5def06e55306aa3601b87de6b5149671529318da048f67cdd77b"
"checksum cocoa 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8cd20045e880893b4a8286d5639e9ade85fb1f6a14c291f882cf8cf2149d37d9"
"checksum color_quant 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0dbbb57365263e881e805dc77d94697c9118fd94d8da011240555aa7b23445bd"
"checksum conrod_core 0.63.0 (git+https://gitlab.com/veloren/conrod.git)" = "<none>"
"checksum conrod_derive 0.63.0 (git+https://gitlab.com/veloren/conrod.git)" = "<none>"
@ -3924,11 +3907,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum const-random 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7b641a8c9867e341f3295564203b1c250eb8ce6cb6126e007941f78c4d2ed7fe"
"checksum const-random-macro 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c750ec12b83377637110d5a57f5ae08e895b06c4b16e2bdbf1a94ef717428c59"
"checksum constant_time_eq 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "995a44c877f9212528ccc74b21a232f66ad69001e40ede5bcee2ac9ef2657120"
"checksum core-foundation 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "286e0b41c3a20da26536c6000a280585d519fd07b3956b43aed8a79e9edce980"
"checksum core-foundation 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "25b9e03f145fd4f2bf705e07b900cd41fc636598fe5dc452fd0db1441c3f496d"
"checksum core-foundation-sys 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "716c271e8613ace48344f723b60b900a93150271e5be206212d052bbc0883efa"
"checksum core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e7ca8a5221364ef15ce201e8ed2f609fc312682a8f4e0e3d4aa5879764e0fa3b"
"checksum core-graphics 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fb0ed45fdc32f9ab426238fba9407dfead7bacd7900c9b4dd3f396f46eafdae3"
"checksum core-graphics 0.17.3 (registry+https://github.com/rust-lang/crates.io-index)" = "56790968ab1c8a1202a102e6de05fc6e1ec87da99e4e93e9a7d13efbfc1e95a9"
"checksum coreaudio-rs 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f229761965dad3e9b11081668a6ea00f1def7aa46062321b5ec245b834f6e491"
"checksum coreaudio-sys 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7e8f5954c1c7ccb55340443e8b29fca24013545a5e7d72c1ca7db4fc02b982ce"
@ -4071,7 +4051,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum mio-extras 2.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "46e73a04c2fa6250b8d802134d56d554a9ec2922bf977777c805ea5def61ce40"
"checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919"
"checksum mopa 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a785740271256c230f57462d3b83e52f998433a7062fc18f96d5999474a9f915"
"checksum msgbox 0.2.0 (git+https://github.com/bekker/msgbox-rs.git)" = "<none>"
"checksum msgbox 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "82cb63d8d7be875323a43d9ab525c28ce2d65bff89648d1aedd9962e00dede00"
"checksum multipart 0.15.4 (registry+https://github.com/rust-lang/crates.io-index)" = "adba94490a79baf2d6a23eac897157047008272fa3eecb3373ae6377b91eca28"
"checksum net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88"
"checksum nix 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6c722bee1037d430d0f8e687bbdbf222f27cc6e4e68d5caf630857bb2b6dbdce"
@ -4170,6 +4150,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e"
"checksum rodio 0.9.0 (git+https://github.com/RustAudio/rodio?rev=e5474a2)" = "<none>"
"checksum ron 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2ece421e0c4129b90e4a35b6f625e472e96c552136f5093a2f4fa2bbb75a62d5"
"checksum roots 0.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e4c67c712ab62be58b24ab8960e2b95dd4ee00aac115c76f2709657821fe376d"
"checksum rouille 3.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "112568052ec17fa26c6c11c40acbb30d3ad244bf3d6da0be181f5e7e42e5004f"
"checksum rust-argon2 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4ca4eaef519b494d1f2848fc602d18816fed808a981aedf4f1f00ceb7c9d32cf"
"checksum rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783"
@ -4246,7 +4227,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
"checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
"checksum url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dd4e7c0d531266369519a4aa4f399d748bd37043b00bde1e4ff1f60a120b355a"
"checksum user32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4ef4711d107b21b410a3a974b1204d9accc8b10dad75d8324b5d755de1617d47"
"checksum utf8-ranges 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b4ae116fef2b7fea257ed6440d3cfcff7f190865f170cdad00bb6465bf18ecba"
"checksum uvth 3.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e59a167890d173eb0fcd7a1b99b84dc05c521ae8d76599130b8e19bef287abbf"
"checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a"

View File

@ -12,8 +12,11 @@ members = [
[profile.dev]
opt-level = 2
overflow-checks = false
panic = "abort"
# debug = false
[profile.release]
# panic = "abort"
debug = false
codegen-units = 1
lto = true

View File

@ -1,6 +1,7 @@
#version 330 core
#include <globals.glsl>
#include <srgb.glsl>
in uint v_pos_norm;
in uint v_col_light;
@ -35,11 +36,11 @@ void main() {
// Use an array to avoid conditional branching
f_norm = normals[norm_axis + norm_dir];
f_col = vec3(
f_col = srgb_to_linear(vec3(
float((v_col_light >> 8) & 0xFFu),
float((v_col_light >> 16) & 0xFFu),
float((v_col_light >> 24) & 0xFFu)
) / 200.0;
) / 255.0);
f_light = float(v_col_light & 0xFFu) / 255.0;

View File

@ -7,7 +7,9 @@ edition = "2018"
[dependencies]
common = { package = "veloren-common", path = "../common" }
byteorder = "1.3.2"
uvth = "3.1.1"
image = "0.22.0"
num_cpus = "1.10.1"
log = "0.4.8"
specs = "0.14.2"

View File

@ -20,6 +20,7 @@ use common::{
ChatType,
};
use hashbrown::HashMap;
use image::DynamicImage;
use log::warn;
use std::{
net::SocketAddr,
@ -43,6 +44,7 @@ pub struct Client {
client_state: ClientState,
thread_pool: ThreadPool,
pub server_info: ServerInfo,
pub world_map: Arc<DynamicImage>,
postbox: PostBox<ClientMsg, ServerMsg>,
@ -67,11 +69,12 @@ impl Client {
let mut postbox = PostBox::to(addr)?;
// Wait for initial sync
let (state, entity, server_info) = match postbox.next_message() {
let (state, entity, server_info, world_map) = match postbox.next_message() {
Some(ServerMsg::InitialSync {
ecs_state,
entity_uid,
server_info,
// world_map: /*(map_size, world_map)*/map_size,
}) => {
// TODO: Voxygen should display this.
if server_info.git_hash != common::util::GIT_HASH.to_string() {
@ -87,7 +90,24 @@ impl Client {
.ecs()
.entity_from_uid(entity_uid)
.ok_or(Error::ServerWentMad)?;
(state, entity, server_info)
// assert_eq!(world_map.len(), map_size.x * map_size.y);
let map_size = Vec2::new(1024, 1024);
let world_map_raw = vec![0u8; 4 * /*world_map.len()*/map_size.x * map_size.y];
// LittleEndian::write_u32_into(&world_map, &mut world_map_raw);
log::info!("Preparing image...");
let world_map = Arc::new(image::DynamicImage::ImageRgba8({
// Should not fail if the dimensions are correct.
let world_map = image::ImageBuffer::from_raw(
map_size.x as u32,
map_size.y as u32,
world_map_raw,
);
world_map.ok_or(Error::Other("Server sent a bad world map image".into()))?
}));
log::info!("Done preparing image...");
(state, entity, server_info, world_map)
}
Some(ServerMsg::Error(ServerError::TooManyPlayers)) => {
return Err(Error::TooManyPlayers)
@ -107,6 +127,7 @@ impl Client {
client_state,
thread_pool,
server_info,
world_map,
postbox,
@ -172,7 +193,7 @@ impl Client {
}
pub fn set_view_distance(&mut self, view_distance: u32) {
self.view_distance = Some(view_distance.max(1).min(25));
self.view_distance = Some(view_distance.max(1).min(65));
self.postbox
.send_message(ClientMsg::SetViewDistance(self.view_distance.unwrap()));
// Can't fail

View File

@ -28,6 +28,7 @@ pub enum ServerMsg {
ecs_state: sphynx::StatePackage<EcsCompPacket, EcsResPacket>,
entity_uid: u64,
server_info: ServerInfo,
// world_map: Vec2<usize>, /*, Vec<u32>)*/
},
StateAnswer(Result<ClientState, (RequestStateError, ClientState)>),
ForceState(ClientState),

View File

@ -10,10 +10,13 @@ use common::{
msg::ServerMsg,
npc::{get_npc_name, NpcKind},
state::TimeOfDay,
terrain::TerrainChunkSize,
vol::RectVolSize,
};
use rand::Rng;
use specs::{Builder, Entity as EcsEntity, Join};
use vek::*;
use world::util::Sampler;
use lazy_static::lazy_static;
use scan_fmt::{scan_fmt, scan_fmt_some};
@ -887,6 +890,7 @@ fn handle_tell(server: &mut Server, entity: EcsEntity, args: String, action: &Ch
fn handle_debug_column(server: &mut Server, entity: EcsEntity, args: String, action: &ChatCommand) {
let sim = server.world.sim();
let sampler = server.world.sample_columns();
if let Ok((x, y)) = scan_fmt!(&args, action.arg_fmt, i32, i32) {
let wpos = Vec2::new(x, y);
/* let chunk_pos = wpos.map2(TerrainChunkSize::RECT_SIZE, |e, sz: u32| {
@ -895,26 +899,48 @@ fn handle_debug_column(server: &mut Server, entity: EcsEntity, args: String, act
let foo = || {
// let sim_chunk = sim.get(chunk_pos)?;
let alt_base = sim.get_interpolated(wpos, |chunk| chunk.alt_base)?;
let alt = sim.get_interpolated(wpos, |chunk| chunk.alt)?;
let water_alt = sim.get_interpolated(wpos, |chunk| chunk.water_alt)?;
let chaos = sim.get_interpolated(wpos, |chunk| chunk.chaos)?;
let temp = sim.get_interpolated(wpos, |chunk| chunk.temp)?;
let humidity = sim.get_interpolated(wpos, |chunk| chunk.humidity)?;
let rockiness = sim.get_interpolated(wpos, |chunk| chunk.rockiness)?;
let tree_density = sim.get_interpolated(wpos, |chunk| chunk.tree_density)?;
let spawn_rate = sim.get_interpolated(wpos, |chunk| chunk.spawn_rate)?;
let chunk_pos = wpos.map2(TerrainChunkSize::RECT_SIZE, |e, sz: u32| e / sz as i32);
let chunk = sim.get(chunk_pos)?;
let col = sampler.get(wpos)?;
let downhill = chunk.downhill;
let river = &chunk.river;
let flux = chunk.flux;
Some(format!(
r#"wpos: {:?}
alt_base {:?}
alt {:?}
alt {:?} ({:?})
water_alt {:?} ({:?})
river {:?}
downhill {:?}
chaos {:?}
flux {:?}
temp {:?}
humidity {:?}
rockiness {:?}
tree_density {:?}
spawn_rate {:?} "#,
wpos, alt_base, alt, chaos, temp, humidity, rockiness, tree_density, spawn_rate
wpos,
alt,
col.alt,
water_alt,
col.water_level,
river,
downhill,
chaos,
flux,
temp,
humidity,
rockiness,
tree_density,
spawn_rate
))
};
if let Some(s) = foo() {

View File

@ -98,7 +98,7 @@ impl Server {
let mut state = State::default();
state
.ecs_mut()
.add_resource(SpawnPoint(Vec3::new(16_384.0, 16_384.0, 190.0)));
.add_resource(SpawnPoint(Vec3::new(16_384.0, 16_384.0, 600.0)));
state
.ecs_mut()
.add_resource(EventBus::<ServerEvent>::default());
@ -867,11 +867,14 @@ impl Server {
client.notify(ServerMsg::Error(ServerError::TooManyPlayers));
} else {
// Return the state of the current world (all of the components that Sphynx tracks).
log::info!("Starting initial sync with client.");
client.notify(ServerMsg::InitialSync {
ecs_state: self.state.ecs().gen_state_package(),
entity_uid: self.state.ecs().uid_from_entity(entity).unwrap().into(), // Can't fail.
server_info: self.server_info.clone(),
// world_map: (WORLD_SIZE/*, self.world.sim().get_map()*/),
});
log::info!("Done initial sync with client.");
frontend_events.push(Event::ClientConnected { entity });
}

View File

@ -50,7 +50,7 @@ serde_derive = "1.0.98"
ron = "0.5.1"
guillotiere = "0.4.2"
simplelog = "0.6.0"
msgbox = { git = "https://github.com/bekker/msgbox-rs.git", optional = true }
msgbox = { version = "0.4.0", optional = true }
directories = "2.0.2"
num = "0.2.0"
backtrace = "0.3.33"

View File

@ -3,6 +3,7 @@ use client::{self, Client};
use common::comp;
use conrod_core::{
color,
image::Id,
widget::{self, Button, Image, Rectangle, Text},
widget_ids, Colorable, Positionable, Sizeable, Widget, WidgetCommon,
};
@ -30,16 +31,24 @@ pub struct Map<'a> {
_show: &'a Show,
client: &'a Client,
_world_map: Id,
imgs: &'a Imgs,
fonts: &'a Fonts,
#[conrod(common_builder)]
common: widget::CommonBuilder,
}
impl<'a> Map<'a> {
pub fn new(show: &'a Show, client: &'a Client, imgs: &'a Imgs, fonts: &'a Fonts) -> Self {
pub fn new(
show: &'a Show,
client: &'a Client,
imgs: &'a Imgs,
world_map: Id,
fonts: &'a Fonts,
) -> Self {
Self {
_show: show,
imgs,
_world_map: world_map,
client,
fonts: fonts,
common: widget::CommonBuilder::default(),
@ -132,7 +141,7 @@ impl<'a> Widget for Map<'a> {
.set(state.ids.location_name, ui),
}
// Map Image
Image::new(self.imgs.map_placeholder)
Image::new(/*self.world_map*/ self.imgs.map_placeholder)
.middle_of(state.ids.map_bg)
.w_h(700.0, 700.0)
.parent(state.ids.map_bg)

View File

@ -3,6 +3,7 @@ use client::{self, Client};
use common::comp;
use conrod_core::{
color,
image::Id,
widget::{self, Button, Image, Rectangle, Text},
widget_ids, Color, Colorable, Positionable, Sizeable, Widget, WidgetCommon,
};
@ -29,17 +30,25 @@ pub struct MiniMap<'a> {
client: &'a Client,
imgs: &'a Imgs,
_world_map: Id,
fonts: &'a Fonts,
#[conrod(common_builder)]
common: widget::CommonBuilder,
}
impl<'a> MiniMap<'a> {
pub fn new(show: &'a Show, client: &'a Client, imgs: &'a Imgs, fonts: &'a Fonts) -> Self {
pub fn new(
show: &'a Show,
client: &'a Client,
imgs: &'a Imgs,
world_map: Id,
fonts: &'a Fonts,
) -> Self {
Self {
show,
client,
imgs,
_world_map: world_map,
fonts: fonts,
common: widget::CommonBuilder::default(),
}
@ -88,7 +97,7 @@ impl<'a> Widget for MiniMap<'a> {
.mid_top_with_margin_on(state.ids.mmap_frame, 13.0 * 2.0 + 2.0)
.set(state.ids.mmap_frame_bg, ui);
// Map Image
Image::new(self.imgs.map_placeholder)
Image::new(/*self.world_map*/ self.imgs.map_placeholder)
.middle_of(state.ids.mmap_frame_bg)
.w_h(92.0 * 2.0, 82.0 * 2.0)
.parent(state.ids.mmap_frame_bg)

View File

@ -37,13 +37,14 @@ use crate::{
render::{AaMode, Consts, Globals, Renderer},
scene::camera::Camera,
//settings::ControlSettings,
ui::{Ingameable, ScaleMode, Ui},
ui::{Graphic, Ingameable, ScaleMode, Ui},
window::{Event as WinEvent, GameInput},
GlobalState,
};
use client::{Client, Event as ClientEvent};
use common::{comp, terrain::TerrainChunk, vol::RectRasterableVol};
use conrod_core::{
image::Id,
text::cursor::Index,
widget::{self, Button, Image, Rectangle, Text},
widget_ids, Color, Colorable, Labelable, Positionable, Sizeable, Widget,
@ -121,6 +122,7 @@ widget_ids! {
// External
chat,
map,
world_map,
character_window,
minimap,
bag,
@ -371,6 +373,7 @@ impl Show {
pub struct Hud {
ui: Ui,
ids: Ids,
world_map: Id,
imgs: Imgs,
item_imgs: ItemImgs,
fonts: Fonts,
@ -385,7 +388,7 @@ pub struct Hud {
}
impl Hud {
pub fn new(global_state: &mut GlobalState) -> Self {
pub fn new(global_state: &mut GlobalState, client: &Client) -> Self {
let window = &mut global_state.window;
let settings = &global_state.settings;
@ -393,6 +396,8 @@ impl Hud {
ui.set_scaling_mode(settings.gameplay.ui_scale);
// Generate ids.
let ids = Ids::new(ui.id_generator());
// Load world map
let world_map = ui.add_graphic(Graphic::Image(client.world_map.clone()));
// Load images.
let imgs = Imgs::load(&mut ui).expect("Failed to load images!");
// Load rotation images.
@ -405,6 +410,7 @@ impl Hud {
Self {
ui,
imgs,
world_map,
rot_imgs,
item_imgs,
fonts,
@ -737,7 +743,7 @@ impl Hud {
}
// MiniMap
match MiniMap::new(&self.show, client, &self.imgs, &self.fonts)
match MiniMap::new(&self.show, client, &self.imgs, self.world_map, &self.fonts)
.set(self.ids.minimap, ui_widgets)
{
Some(minimap::Event::Toggle) => self.show.toggle_mini_map(),
@ -923,7 +929,7 @@ impl Hud {
}
// Map
if self.show.map {
match Map::new(&self.show, client, &self.imgs, &self.fonts)
match Map::new(&self.show, client, &self.imgs, self.world_map, &self.fonts)
.set(self.ids.map, ui_widgets)
{
Some(map::Event::Close) => {

View File

@ -1139,7 +1139,7 @@ impl<'a> Widget for SettingsWindow<'a> {
if let Some(new_val) = ImageSlider::discrete(
self.global_state.settings.graphics.view_distance,
1,
25,
65,
self.imgs.slider_indicator,
self.imgs.slider,
)

View File

@ -38,12 +38,13 @@ impl SessionState {
scene
.camera_mut()
.set_fov_deg(global_state.settings.graphics.fov);
let hud = Hud::new(global_state, &client.borrow());
Self {
scene,
client,
key_state: KeyState::new(),
controller: comp::Controller::default(),
hud: Hud::new(global_state),
hud,
selected_block: Block::new(BlockKind::Normal, Rgb::broadcast(255)),
}
}

View File

@ -6,13 +6,19 @@ edition = "2018"
[dependencies]
common = { package = "veloren-common", path = "../common" }
bitvec = "0.15.1"
vek = "0.9.9"
noise = "0.5.1"
num = "0.2.0"
ordered-float = "1.0"
hashbrown = { version = "0.6.0", features = ["serde"] }
lazy_static = "1.4.0"
log = "0.4.8"
rand = "0.7.2"
rand_chacha = "0.2.1"
arr_macro = "0.1.2"
rayon = "1.1.0"
roots = "0.0.5"
serde = "1.0.101"
ron = "0.5.1"

View File

@ -1,5 +1,5 @@
use rand::thread_rng;
use std::ops::{Add, Mul, Sub};
use vek::*;
use veloren_world::sim::Settlement;

View File

@ -1,8 +1,6 @@
use noise::{NoiseFn, Seedable, SuperSimplex};
use rand::thread_rng;
use std::ops::{Add, Mul, Sub};
use noise::{Seedable, SuperSimplex};
use vek::*;
use veloren_world::sim::Settlement;
const W: usize = 640;
const H: usize = 640;
@ -10,8 +8,8 @@ const H: usize = 640;
fn main() {
let mut win = minifb::Window::new("Turb", W, H, minifb::WindowOptions::default()).unwrap();
let nz_x = SuperSimplex::new().set_seed(0);
let nz_y = SuperSimplex::new().set_seed(1);
let _nz_x = SuperSimplex::new().set_seed(0);
let _nz_y = SuperSimplex::new().set_seed(1);
let mut time = 0.0f64;
while win.is_open() {

87
world/examples/water.rs Normal file
View File

@ -0,0 +1,87 @@
use vek::*;
use veloren_world::{
sim::{RiverKind, WORLD_SIZE},
util::Sampler,
World, CONFIG,
};
const W: usize = 1024;
const H: usize = 1024;
fn main() {
let world = World::generate(1337);
let sampler = world.sim();
let mut win =
minifb::Window::new("World Viewer", W, H, minifb::WindowOptions::default()).unwrap();
let mut focus = Vec2::zero();
let mut gain = 1.0;
let mut scale = (WORLD_SIZE.x / W) as i32;
while win.is_open() {
let mut buf = vec![0; W * H];
for i in 0..W {
for j in 0..H {
let pos = focus + Vec2::new(i as i32, j as i32) * scale;
let (alt, water_alt, river_kind) = sampler
.get(pos)
.map(|sample| (sample.alt, sample.water_alt, sample.river.river_kind))
.unwrap_or((CONFIG.sea_level, CONFIG.sea_level, None));
let alt = ((alt - CONFIG.sea_level) / CONFIG.mountain_scale)
.min(1.0)
.max(0.0);
let water_alt = ((alt.max(water_alt) - CONFIG.sea_level) / CONFIG.mountain_scale)
.min(1.0)
.max(0.0);
buf[j * W + i] = match river_kind {
Some(RiverKind::Ocean) => u32::from_le_bytes([64, 32, 0, 255]),
Some(RiverKind::Lake { .. }) => u32::from_le_bytes([
64 + (water_alt * 191.0) as u8,
32 + (water_alt * 95.0) as u8,
0,
255,
]),
Some(RiverKind::River { .. }) => u32::from_le_bytes([
64 + (alt * 191.0) as u8,
32 + (alt * 95.0) as u8,
0,
255,
]),
None => u32::from_le_bytes([0, (alt * 255.0) as u8, 0, 255]),
};
}
}
let spd = 32;
if win.is_key_down(minifb::Key::W) {
focus.y -= spd * scale;
}
if win.is_key_down(minifb::Key::A) {
focus.x -= spd * scale;
}
if win.is_key_down(minifb::Key::S) {
focus.y += spd * scale;
}
if win.is_key_down(minifb::Key::D) {
focus.x += spd * scale;
}
if win.is_key_down(minifb::Key::Q) {
gain += 10.0;
}
if win.is_key_down(minifb::Key::E) {
gain -= 10.0;
}
if win.is_key_down(minifb::Key::R) {
scale += 1;
}
if win.is_key_down(minifb::Key::F) {
scale = (scale - 1).max(0);
}
win.update_with_buffer(&buf).unwrap();
}
}

View File

@ -150,8 +150,8 @@ impl<'a> BlockGen<'a> {
let &ColumnSample {
alt,
chaos,
water_level: _,
//river,
water_level,
warp_factor,
surface_color,
sub_surface_color,
//tree_density,
@ -179,7 +179,7 @@ impl<'a> BlockGen<'a> {
let (_definitely_underground, height, on_cliff, water_height) =
if (wposf.z as f32) < alt - 64.0 * chaos {
// Shortcut warping
(true, alt, false, CONFIG.sea_level /*water_level*/)
(true, alt, false, water_level)
} else {
// Apply warping
let warp = world
@ -189,6 +189,7 @@ impl<'a> BlockGen<'a> {
.get(wposf.div(24.0))
.mul((chaos - 0.1).max(0.0).powf(2.0))
.mul(48.0);
let warp = Lerp::lerp(0.0, warp, warp_factor);
let surface_height = alt + warp;
@ -221,7 +222,11 @@ impl<'a> BlockGen<'a> {
false,
height,
on_cliff,
/*(water_level + warp).max(*/ CONFIG.sea_level, /*)*/
(if water_level <= alt {
water_level + warp
} else {
water_level
}),
)
};
@ -371,7 +376,7 @@ impl<'a> BlockGen<'a> {
.add(1.0)
> 0.9993;
if cave {
if cave && wposf.z as f32 > water_height + 3.0 {
None
} else {
Some(block)
@ -420,13 +425,15 @@ pub struct ZCache<'a> {
impl<'a> ZCache<'a> {
pub fn get_z_limits(&self, block_gen: &mut BlockGen) -> (f32, f32, f32) {
let cave_depth = if self.sample.cave_xy.abs() > 0.9 {
(self.sample.alt - self.sample.cave_alt + 8.0).max(0.0)
} else {
0.0
};
let cave_depth =
if self.sample.cave_xy.abs() > 0.9 && self.sample.water_level <= self.sample.alt {
(self.sample.alt - self.sample.cave_alt + 8.0).max(0.0)
} else {
0.0
};
let min = self.sample.alt - (self.sample.chaos * 48.0 + cave_depth) - 4.0;
let min = self.sample.alt - (self.sample.chaos * 48.0 + cave_depth);
let min = min - 4.0;
let cliff = BlockGen::get_cliff_height(
&mut block_gen.column_gen,
@ -462,9 +469,7 @@ impl<'a> ZCache<'a> {
let ground_max = (self.sample.alt + warp + rocks).max(cliff) + 2.0;
let min = min + structure_min;
let max = (ground_max + structure_max)
.max(self.sample.water_level)
.max(CONFIG.sea_level + 2.0);
let max = (ground_max + structure_max).max(self.sample.water_level + 2.0);
// Structures
let (min, max) = self
@ -479,9 +484,7 @@ impl<'a> ZCache<'a> {
})
.unwrap_or((min, max));
let structures_only_min_z = ground_max
.max(self.sample.water_level)
.max(CONFIG.sea_level + 2.0);
let structures_only_min_z = ground_max.max(self.sample.water_level + 2.0);
(min, structures_only_min_z, max)
}

View File

@ -2,7 +2,10 @@ use crate::{
all::ForestKind,
block::StructureMeta,
generator::{Generator, SpawnRules, TownGen},
sim::{LocationInfo, SimChunk, WorldSim},
sim::{
local_cells, uniform_idx_as_vec2, vec2_as_uniform_idx, LocationInfo, RiverKind, SimChunk,
WorldSim,
},
util::{RandomPerm, Sampler, UnitChooser},
CONFIG,
};
@ -13,8 +16,10 @@ use common::{
};
use lazy_static::lazy_static;
use noise::NoiseFn;
use roots::find_roots_cubic;
use std::{
f32,
cmp::Reverse,
f32, f64,
ops::{Add, Div, Mul, Neg, Sub},
sync::Arc,
};
@ -76,14 +81,15 @@ impl<'a> ColumnGen<'a> {
if seed % 5 == 2
&& chunk.temp > CONFIG.desert_temp
&& chunk.alt > CONFIG.sea_level + 5.0
&& chunk.alt > chunk.water_alt + 5.0
&& chunk.chaos <= 0.35
{
Some(StructureData {
/*Some(StructureData {
pos,
seed,
meta: Some(StructureMeta::Pyramid { height: 140 }),
})
})*/
None
} else if seed % 17 == 2 && chunk.chaos < 0.2 {
Some(StructureData {
pos,
@ -118,6 +124,92 @@ impl<'a> ColumnGen<'a> {
}
}
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).
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
.into_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> {
type Index = Vec2<i32>;
type Sample = Option<ColumnSample<'a>>;
@ -134,29 +226,328 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
) * 12.0;
let wposf_turb = wposf + turb.map(|e| e as f64);
let alt_base = sim.get_interpolated(wpos, |chunk| chunk.alt_base)?;
let chaos = sim.get_interpolated(wpos, |chunk| chunk.chaos)?;
let temp = sim.get_interpolated(wpos, |chunk| chunk.temp)?;
let humidity = sim.get_interpolated(wpos, |chunk| chunk.humidity)?;
let rockiness = sim.get_interpolated(wpos, |chunk| chunk.rockiness)?;
let tree_density = sim.get_interpolated(wpos, |chunk| chunk.tree_density)?;
let spawn_rate = sim.get_interpolated(wpos, |chunk| chunk.spawn_rate)?;
let alt = sim.get_interpolated_monotone(wpos, |chunk| chunk.alt)?;
let sim_chunk = sim.get(chunk_pos)?;
let neighbor_coef = TerrainChunkSize::RECT_SIZE.map(|e| e as f64);
let my_chunk_idx = vec2_as_uniform_idx(chunk_pos);
let neighbor_river_data = local_cells(my_chunk_idx).filter_map(|neighbor_idx: usize| {
let neighbor_pos = uniform_idx_as_vec2(neighbor_idx);
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 neighbor_river_data = neighbor_river_data.map(|(posj, chunkj, river)| {
let kind = match river.river_kind {
Some(kind) => kind,
None => {
return (posj, chunkj, river, None);
}
};
let downhill_pos = if let Some(pos) = chunkj.downhill {
pos
} else {
match kind {
RiverKind::River { .. } => {
log::error!("What? River: {:?}, Pos: {:?}", river, posj);
panic!("How can a river have no downhill?");
}
RiverKind::Lake { .. } => {
return (posj, chunkj, river, None);
}
RiverKind::Ocean => posj,
}
};
let downhill_wpos = downhill_pos.map(|e| e as f64);
let downhill_pos =
downhill_pos.map2(TerrainChunkSize::RECT_SIZE, |e, sz: u32| e / sz as i32);
let neighbor_pos = posj.map(|e| e as f64) * neighbor_coef;
let direction = neighbor_pos - downhill_wpos;
let river_width_min = if let RiverKind::River { cross_section } = kind {
cross_section.x as f64
} else {
lake_width
};
let downhill_chunk = sim.get(downhill_pos).expect("How can this not work?");
let coeffs =
river_spline_coeffs(neighbor_pos, chunkj.river.spline_derivative, downhill_wpos);
let (direction, coeffs, downhill_chunk, river_t, river_pos, river_dist) = match kind {
RiverKind::River { .. } => {
if let Some((t, pt, dist)) = quadratic_nearest_point(&coeffs, wposf) {
(direction, coeffs, downhill_chunk, t, pt, dist.sqrt())
} else {
let ndist = wposf.distance_squared(neighbor_pos);
let ddist = wposf.distance_squared(downhill_wpos);
let (closest_pos, closest_dist, closest_t) = if ndist <= ddist {
(neighbor_pos, ndist, 0.0)
} else {
(downhill_wpos, ddist, 1.0)
};
(
direction,
coeffs,
downhill_chunk,
closest_t,
closest_pos,
closest_dist.sqrt(),
)
}
}
RiverKind::Lake { neighbor_pass_pos } => {
let pass_dist = neighbor_pass_pos
.map2(
neighbor_pos
.map2(TerrainChunkSize::RECT_SIZE, |f, g| (f as i32, g as i32)),
|e, (f, g)| ((e - f) / g).abs(),
)
.reduce_partial_max();
let spline_derivative = river.spline_derivative;
let neighbor_pass_pos = if pass_dist <= 1 {
neighbor_pass_pos
} else {
downhill_wpos.map(|e| e as i32)
};
let pass_dist = neighbor_pass_pos
.map2(
neighbor_pos
.map2(TerrainChunkSize::RECT_SIZE, |f, g| (f as i32, g as i32)),
|e, (f, g)| ((e - f) / g).abs(),
)
.reduce_partial_max();
if pass_dist > 1 {
return (posj, chunkj, river, None);
}
let neighbor_pass_wpos = neighbor_pass_pos.map(|e| e as f64);
let neighbor_pass_pos = neighbor_pass_pos
.map2(TerrainChunkSize::RECT_SIZE, |e, sz: u32| e / sz as i32);
let coeffs =
river_spline_coeffs(neighbor_pos, spline_derivative, neighbor_pass_wpos);
let direction = neighbor_pos - neighbor_pass_wpos;
if let Some((t, pt, dist)) = quadratic_nearest_point(&coeffs, wposf) {
(
direction,
coeffs,
sim.get(neighbor_pass_pos).expect("Must already work"),
t,
pt,
dist.sqrt(),
)
} else {
let ndist = wposf.distance_squared(neighbor_pos);
/* let ddist = wposf.distance_squared(neighbor_pass_wpos); */
let (closest_pos, closest_dist, closest_t) = /*if ndist <= ddist */ {
(neighbor_pos, ndist, 0.0)
} /* else {
(neighbor_pass_wpos, ddist, 1.0)
} */;
(
direction,
coeffs,
sim.get(neighbor_pass_pos).expect("Must already work"),
closest_t,
closest_pos,
closest_dist.sqrt(),
)
}
}
RiverKind::Ocean => {
let ndist = wposf.distance_squared(neighbor_pos);
let (closest_pos, closest_dist, closest_t) = (neighbor_pos, ndist, 0.0);
(
direction,
coeffs,
sim.get(closest_pos.map2(TerrainChunkSize::RECT_SIZE, |e, sz: u32| {
e as i32 / sz as i32
}))
.expect("Must already work"),
closest_t,
closest_pos,
closest_dist.sqrt(),
)
}
};
let river_width_max =
if let Some(RiverKind::River { cross_section }) = downhill_chunk.river.river_kind {
cross_section.x as f64
} else {
lake_width
};
let river_width_noise = (sim.gen_ctx.small_nz.get((river_pos.div(16.0)).into_array()))
.max(-1.0)
.min(1.0)
.mul(0.5)
.sub(0.5) as f64;
let river_width = Lerp::lerp(
river_width_min,
river_width_max,
river_t.max(0.0).min(1.0).powf(0.5),
);
// Never used
//const RIVER_PROPORTION: f32 = 0.025;
let river_width = river_width * (1.0 + river_width_noise * 0.3);
// To find the distance, we just evaluate the quadratic equation at river_t and see
// if it's within width (but we should be able to use it for a lot more, and this
// probably isn't the very best approach anyway since it will bleed out).
// let river_pos = coeffs.x * river_t * river_t + coeffs.y * river_t + coeffs.z;
let res = Vec2::new(0.0, (river_dist - (river_width * 0.5).max(1.0)).max(0.0));
(
posj,
chunkj,
river,
Some((
direction,
res,
river_width,
(river_t, (river_pos, coeffs), downhill_chunk),
)),
)
});
/*
let river = dryness
.abs()
.neg()
.add(RIVER_PROPORTION)
.div(RIVER_PROPORTION)
.max(0.0)
.mul((1.0 - (chaos - 0.15) * 20.0).max(0.0).min(1.0));
*/
let river = 0.0;
// Find the average distance to each neighboring body of water.
let mut river_count = 0.0f64;
let mut overlap_count = 0.0f64;
let mut river_distance_product = 1.0f64;
let mut river_overlap_distance_product = 0.0f64;
let mut max_river = None;
let mut max_key = None;
// IDEA:
// For every "nearby" chunk, check whether it is a river. If so, find the closest point on
// the river segment to wposf (if two point are equidistant, choose the earlier one),
// calling this point river_pos and the length (from 0 to 1) along the river segment for
// the nearby chunk river_t. Let river_dist be the distance from river_pos to wposf.
//
// Let river_alt be the interpolated river height at this point
// (from the alt/water altitude at the river, to the alt/water_altitude of the downhill
// river, increasing with river_t).
//
// Now, if river_dist is <= river_width * 0.5, then we don't care what altitude we use, and
// mark that we are on a river (we decide what river to use using a heuristic, and set the
// solely according to the computed river_alt for that point).
//
// Otherwise, we let dist = river_dist - river_width * 0.5.
//
// If dist >= TerrainChunkSize::RECT_SIZE.x, we don't include this river in the calculation
// of the correct altitude for this point.
//
// Otherwise (i.e. dist < TerrainChunkSize::RECT_SIZE.x), we want to bias the altitude of
// this point towards the altitude of the river. Specifically, as the dist goes from
// TerrainChunkSize::RECT_SIZE.x to 0, the weighted altitude of this point should go from
// alt to river_alt.
for (river_chunk_idx, river_chunk, river, dist) in neighbor_river_data {
match river.river_kind {
Some(kind) => {
if kind.is_river() && !dist.is_some() {
// Ostensibly near a river segment, but not "usefully" so (there is no
// closest point between t = 0.0 and t = 1.0).
continue;
} else {
let river_dist = dist.map(|(_, dist, _, (river_t, _, downhill_river))| {
let downhill_height = if kind.is_river() {
Lerp::lerp(
river_chunk.alt.max(river_chunk.water_alt),
downhill_river.alt.max(downhill_river.water_alt),
river_t as f32,
) as f64
} else {
let neighbor_pos =
river_chunk_idx.map(|e| e as f64) * neighbor_coef;
if dist.y == 0.0 {
-(wposf - neighbor_pos).magnitude()
} else {
-(wposf - neighbor_pos).magnitude()
}
};
(Reverse((dist.x, dist.y)), downhill_height)
});
let river_dist = river_dist.or_else(|| {
if !kind.is_river() {
let neighbor_pos =
river_chunk_idx.map(|e| e as f64) * neighbor_coef;
let dist = (wposf - neighbor_pos).magnitude();
let dist_upon =
(dist - TerrainChunkSize::RECT_SIZE.x as f64 * 0.5).max(0.0);
let dist_ = if dist == 0.0 { f64::INFINITY } else { -dist };
Some((Reverse((0.0, dist_upon)), dist_))
} else {
None
}
});
let river_key = (river_dist, Reverse(kind));
if max_key < Some(river_key) {
max_river = Some((river_chunk_idx, river_chunk, river, dist));
max_key = Some(river_key);
}
}
// NOTE: we scale by the distance to the river divided by the difference
// between the edge of the river that we intersect, and the remaining distance
// until the nearest point in "this" chunk (i.e. the one whose top-left corner
// is chunk_pos) that is at least 2 chunks away from the river source.
if let Some((_, dist, _, (river_t, _, downhill_river_chunk))) = dist {
let max_distance = if !river.is_river() {
/*(*/
TerrainChunkSize::RECT_SIZE.x as f64 /* * (1.0 - (2.0f64.sqrt() / 2.0))) + 4.0*/ - lake_width * 0.5
} else {
TerrainChunkSize::RECT_SIZE.x as f64
};
let scale_factor = max_distance;
let river_dist = dist.y;
if !(dist.x == 0.0 && river_dist < scale_factor) {
continue;
}
// We basically want to project outwards from river_pos, along the current
// tangent line, to chunks <= river_width * 1.0 away from this
// point. We *don't* want to deal with closer chunks because they
// NOTE: river_width <= 2 * max terrain chunk size width, so this should not
// lead to division by zero.
// NOTE: If distance = 0.0 this goes to zero, which is desired since it
// means points that actually intersect with rivers will not be interpolated
// with the "normal" height of this point.
// NOTE: We keep the maximum at 1.0 so we don't undo work from another river
// just by being far away.
let river_scale = river_dist / scale_factor;
let river_alt =
Lerp::lerp(river_chunk.alt, downhill_river_chunk.alt, river_t as f32);
let river_alt = Lerp::lerp(river_alt, alt, river_scale as f32);
let river_alt_diff = river_alt - alt;
let river_alt_inv = river_alt_diff as f64;
river_overlap_distance_product += (1.0 - river_scale) * river_alt_inv;
overlap_count += 1.0 - river_scale;
river_count += 1.0;
river_distance_product *= river_scale;
}
}
None => {}
}
}
let river_scale_factor = if river_count == 0.0 {
1.0
} else {
let river_scale_factor = river_distance_product;
if river_scale_factor == 0.0 {
0.0
} else {
river_scale_factor.powf(if river_count == 0.0 {
1.0
} else {
1.0 / river_count
})
}
};
let alt_for_river = alt
+ if overlap_count == 0.0 {
0.0
} else {
river_overlap_distance_product / overlap_count
} as f32;
let cliff_hill = (sim
.gen_ctx
@ -164,14 +555,13 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
.get((wposf_turb.div(128.0)).into_array()) as f32)
.mul(24.0);
let riverless_alt = sim.get_interpolated(wpos, |chunk| chunk.alt)?
+ (sim
.gen_ctx
.small_nz
.get((wposf_turb.div(200.0)).into_array()) as f32)
.abs()
.mul(chaos.max(0.05))
.mul(55.0)
let riverless_alt_delta = (sim
.gen_ctx
.small_nz
.get((wposf_turb.div(200.0)).into_array()) as f32)
.abs()
.mul(chaos.max(0.05))
.mul(27.0)
+ (sim
.gen_ctx
.small_nz
@ -179,20 +569,239 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
.abs()
.mul((1.0 - chaos).max(0.3))
.mul(1.0 - humidity)
.mul(65.0);
.mul(32.0);
let downhill = sim_chunk.downhill;
let downhill_pos = downhill.and_then(|downhill_pos| sim.get(downhill_pos));
debug_assert!(sim_chunk.water_alt >= CONFIG.sea_level);
let downhill_water_alt = downhill_pos
.map(|downhill_chunk| {
downhill_chunk
.water_alt
.min(sim_chunk.water_alt)
.max(sim_chunk.alt.min(sim_chunk.water_alt))
})
.unwrap_or(CONFIG.sea_level);
let is_cliffs = sim_chunk.is_cliffs;
let near_cliffs = sim_chunk.near_cliffs;
let alt = riverless_alt
- (1.0 - river)
.mul(f32::consts::PI)
.cos()
.add(1.0)
.mul(0.5)
.mul(24.0);
let river_gouge = 0.5;
let (in_water, alt_, water_level, warp_factor) = if let Some((
max_border_river_pos,
river_chunk,
max_border_river,
max_border_river_dist,
)) = max_river
{
// This is flowing into a lake, or a lake, or is at least a non-ocean tile.
//
// If we are <= water_alt, we are in the lake; otherwise, we are flowing into it.
let (in_water, new_alt, new_water_alt, warp_factor) = max_border_river
.river_kind
.and_then(|river_kind| {
if let RiverKind::River { cross_section } = river_kind {
if max_border_river_dist.map(|(_, dist, _, _)| dist) != Some(Vec2::zero()) {
return None;
}
let (_, _, river_width, (river_t, (river_pos, _), downhill_river_chunk)) =
max_border_river_dist.unwrap();
let river_alt = Lerp::lerp(
river_chunk.alt.max(river_chunk.water_alt),
downhill_river_chunk.alt.max(downhill_river_chunk.water_alt),
river_t as f32,
);
let new_alt = river_alt - river_gouge;
let river_dist = wposf.distance(river_pos);
let river_height_factor = river_dist / (river_width * 0.5);
let water_level = riverless_alt - 4.0 - 5.0 * chaos;
Some((
true,
Lerp::lerp(
new_alt - cross_section.y.max(1.0),
new_alt - 1.0,
(river_height_factor * river_height_factor) as f32,
),
new_alt,
0.0,
))
} else {
None
}
})
.unwrap_or_else(|| {
max_border_river
.river_kind
.and_then(|river_kind| {
match river_kind {
RiverKind::Ocean => {
let (
_,
dist,
river_width,
(river_t, (river_pos, _), downhill_river_chunk),
) = if let Some(dist) = max_border_river_dist {
dist
} else {
log::error!(
"Ocean: {:?} Here: {:?}, Ocean: {:?}",
max_border_river,
chunk_pos,
max_border_river_pos
);
panic!(
"Oceans should definitely have a downhill! ...Right?"
);
};
let lake_water_alt = Lerp::lerp(
river_chunk.alt.max(river_chunk.water_alt),
downhill_river_chunk
.alt
.max(downhill_river_chunk.water_alt),
river_t as f32,
);
if dist == Vec2::zero() {
let river_dist = wposf.distance(river_pos);
let _river_height_factor = river_dist / (river_width * 0.5);
return Some((
true,
alt_for_river.min(lake_water_alt - 1.0 - river_gouge),
lake_water_alt - river_gouge,
0.0,
));
}
Some((
river_scale_factor <= 1.0,
alt_for_river,
downhill_water_alt,
river_scale_factor as f32,
))
}
RiverKind::Lake { .. } => {
let lake_dist = (max_border_river_pos.map(|e| e as f64)
* neighbor_coef)
.distance(wposf);
let downhill_river_chunk = max_border_river_pos;
let lake_id_dist = downhill_river_chunk - chunk_pos;
let in_bounds = lake_id_dist.x >= -1
&& lake_id_dist.y >= -1
&& lake_id_dist.x <= 1
&& lake_id_dist.y <= 1;
let in_bounds =
in_bounds && (lake_id_dist.x >= 0 && lake_id_dist.y >= 0);
let (_, dist, _, (river_t, _, downhill_river_chunk)) =
if let Some(dist) = max_border_river_dist {
dist
} else {
if lake_dist
<= TerrainChunkSize::RECT_SIZE.x as f64 * 1.0
|| in_bounds
{
let gouge_factor = 0.0;
return Some((
in_bounds
|| downhill_water_alt
.max(river_chunk.water_alt)
> alt_for_river,
alt_for_river,
(downhill_water_alt.max(river_chunk.water_alt)
- river_gouge),
river_scale_factor as f32
* (1.0 - gouge_factor),
));
} else {
return Some((
false,
alt_for_river,
downhill_water_alt,
river_scale_factor as f32,
));
}
};
let lake_dist = dist.y;
let lake_water_alt = Lerp::lerp(
river_chunk.alt.max(river_chunk.water_alt),
downhill_river_chunk
.alt
.max(downhill_river_chunk.water_alt),
river_t as f32,
);
if dist == Vec2::zero() {
return Some((
true,
alt_for_river.min(lake_water_alt - 1.0 - river_gouge),
lake_water_alt - river_gouge,
0.0,
));
}
if lake_dist <= TerrainChunkSize::RECT_SIZE.x as f64 * 1.0
|| in_bounds
{
let gouge_factor = if in_bounds && lake_dist <= 1.0 {
1.0
} else {
0.0
};
let in_bounds_ =
lake_dist <= TerrainChunkSize::RECT_SIZE.x as f64 * 0.5;
if gouge_factor == 1.0 {
return Some((
alt_for_river < lake_water_alt || in_bounds,
alt.min(lake_water_alt - 1.0 - river_gouge),
downhill_water_alt.max(lake_water_alt)
- river_gouge,
0.0,
));
} else {
return Some((
alt_for_river < lake_water_alt || in_bounds,
alt_for_river,
if in_bounds_ {
downhill_water_alt.max(lake_water_alt)
- river_gouge
} else {
downhill_water_alt - river_gouge
},
river_scale_factor as f32 * (1.0 - gouge_factor),
));
}
}
Some((
river_scale_factor <= 1.0,
alt_for_river,
downhill_water_alt,
river_scale_factor as f32,
))
}
RiverKind::River { .. } => {
// FIXME: Make water altitude accurate.
Some((
river_scale_factor <= 1.0,
alt_for_river,
downhill_water_alt,
river_scale_factor as f32,
))
}
}
})
.unwrap_or((
false,
alt_for_river,
downhill_water_alt,
river_scale_factor as f32,
))
});
(in_water, new_alt, new_water_alt, warp_factor)
} else {
(false, alt_for_river, downhill_water_alt, 1.0)
};
let riverless_alt_delta = Lerp::lerp(0.0, riverless_alt_delta, warp_factor);
let alt = alt_ + riverless_alt_delta;
let rock = (sim.gen_ctx.small_nz.get(
Vec3::new(wposf.x, wposf.y, alt as f64)
@ -380,68 +989,6 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
.add((marble_small - 0.5) * 0.5),
);
/*
// Work out if we're on a path or near a town
let dist_to_path = match &sim_chunk.location {
Some(loc) => {
let this_loc = &sim.locations[loc.loc_idx];
this_loc
.neighbours
.iter()
.map(|j| {
let other_loc = &sim.locations[*j];
// Find the two location centers
let near_0 = this_loc.center.map(|e| e as f32);
let near_1 = other_loc.center.map(|e| e as f32);
// Calculate distance to path between them
(0.0 + (near_1.y - near_0.y) * wposf_turb.x as f32
- (near_1.x - near_0.x) * wposf_turb.y as f32
+ near_1.x * near_0.y
- near_0.x * near_1.y)
.abs()
.div(near_0.distance(near_1))
})
.filter(|x| x.is_finite())
.min_by(|a, b| a.partial_cmp(b).unwrap())
.unwrap_or(f32::INFINITY)
}
None => f32::INFINITY,
};
let on_path = dist_to_path < 5.0 && !sim_chunk.near_cliffs; // || near_0.distance(wposf_turb.map(|e| e as f32)) < 150.0;
let (alt, ground) = if on_path {
(alt - 1.0, dirt)
} else {
(alt, ground)
};
*/
// Cities
// TODO: In a later MR
/*
let building = match &sim_chunk.location {
Some(loc) => {
let loc = &sim.locations[loc.loc_idx];
let rpos = wposf.map2(loc.center, |a, b| a as f32 - b as f32) / 256.0 + 0.5;
if rpos.map(|e| e >= 0.0 && e < 1.0).reduce_and() {
(loc.settlement
.get_at(rpos)
.map(|b| b.seed % 20 + 10)
.unwrap_or(0)) as f32
} else {
0.0
}
}
None => 0.0,
};
let alt = alt + building;
*/
// Caves
let cave_at = |wposf: Vec2<f64>| {
(sim.gen_ctx.cave_0_nz.get(
@ -470,34 +1017,33 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
.powf(15.0)
.mul(150.0);
let near_ocean = max_river.and_then(|(_, _, river_data, _)| {
if (river_data.is_lake() || river_data.river_kind == Some(RiverKind::Ocean))
&& ((alt <= water_level.max(CONFIG.sea_level + 5.0) && !is_cliffs) || !near_cliffs)
{
Some(water_level)
} else {
None
}
});
let ocean_level = if let Some(_sea_level) = near_ocean {
alt - CONFIG.sea_level
} else {
5.0
};
Some(ColumnSample {
alt,
chaos,
water_level,
river,
warp_factor,
surface_color: Rgb::lerp(
sand,
// Land
Rgb::lerp(
ground,
// Mountain
Rgb::lerp(
cliff,
snow,
(alt - CONFIG.sea_level
- 0.4 * CONFIG.mountain_scale
- alt_base
- temp * 96.0
- marble * 24.0)
/ 12.0,
),
(alt - CONFIG.sea_level - 0.25 * CONFIG.mountain_scale + marble * 128.0)
/ (0.25 * CONFIG.mountain_scale),
),
ground,
// Beach
((alt - CONFIG.sea_level - 1.0) / 2.0)
.min(1.0 - river * 2.0)
.max(0.0),
((ocean_level - 1.0) / 2.0).max(0.0),
),
sub_surface_color: dirt,
tree_density,
@ -523,7 +1069,11 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
.town
.as_ref()
.map(|town| TownGen.spawn_rules(town, wpos))
.unwrap_or(SpawnRules::default()),
.unwrap_or(SpawnRules::default())
.and(SpawnRules {
cliffs: !in_water,
trees: true,
}),
})
}
}
@ -533,7 +1083,7 @@ pub struct ColumnSample<'a> {
pub alt: f32,
pub chaos: f32,
pub water_level: f32,
pub river: f32,
pub warp_factor: f32,
pub surface_color: Rgb<f32>,
pub sub_surface_color: Rgb<f32>,
pub tree_density: f32,

View File

@ -7,15 +7,54 @@ pub struct Config {
pub desert_hum: f32,
pub forest_hum: f32,
pub jungle_hum: f32,
/// Rainfall (in meters) per chunk per minute. Default is set to make it approximately
/// 1 m rainfall / year uniformly across the whole land area, which is the average rainfall
/// on Earth.
pub rainfall_chunk_rate: f32,
/// Roughness coefficient is an empirical value that controls the rate of energy loss of water
/// in a river. The higher it is, the more water slows down as it flows downhill, which
/// consequently leads to lower velocities and higher river area for the same flow rate.
///
/// See https://wwwrcamnl.wr.usgs.gov/sws/fieldmethods/Indirects/nvalues/index.htm.
///
/// The default is set to over 0.06, which is pretty high but still within a reasonable range for
/// rivers. The higher this is, the quicker rivers appear, and since we often will have high
/// slopes we want to give rivers as much of a chance as possible. In the future we can set
/// this dynamically.
///
/// NOTE: The values in the link are in seconds / (m^(-1/3)), but we use them without
/// conversion as though they are in minutes / (m^(-1/3)). The idea here is that our clock
/// speed has time go by at approximately 1 minute per second, but since velocity depends on
/// this parameter, we want flow rates to still "look" natural at the second level. The way we
/// are cheating is that we still allow the refill rate (via rainfall) of rivers and lakes to
/// be specified as though minutes are *really* minutes. This reduces the amount of water
/// needed to form a river of a given area by 60, but hopefully this should not feel too
/// unnatural since the refill rate is still below what people should be able to perceive.
pub river_roughness: f32,
/// Maximum width of rivers, in terms of a multiple of the horizontal chunk size.
///
/// Currently, not known whether setting this above 1.0 will work properly. Please use with
/// care!
pub river_max_width: f32,
/// Minimum height at which rivers display.
pub river_min_height: f32,
/// Rough desired river width-to-depth ratio (in terms of horizontal chunk width / m, for some
/// reason). Not exact.
pub river_width_to_depth: f32,
}
pub const CONFIG: Config = Config {
sea_level: 140.0,
mountain_scale: 1000.0,
mountain_scale: 2048.0,
snow_temp: -0.6,
tropical_temp: 0.2,
desert_temp: 0.6,
desert_hum: 0.15,
forest_hum: 0.5,
jungle_hum: 0.85,
rainfall_chunk_rate: 1.0 / 512.0,
river_roughness: 0.06125,
river_max_width: 2.0,
river_min_height: 0.25,
river_width_to_depth: 1.0,
};

View File

@ -69,16 +69,18 @@ impl World {
let stone = Block::new(BlockKind::Dense, Rgb::new(200, 220, 255));
let water = Block::new(BlockKind::Water, Rgb::new(60, 90, 190));
let chunk_size2d = TerrainChunkSize::RECT_SIZE;
let _chunk_size2d = TerrainChunkSize::RECT_SIZE;
let (base_z, sim_chunk) = match self
.sim
.get_interpolated(
/*.get_interpolated(
chunk_pos.map2(chunk_size2d, |e, sz: u32| e * sz as i32 + sz as i32 / 2),
|chunk| chunk.get_base_z(),
)
.and_then(|base_z| self.sim.get(chunk_pos).map(|sim_chunk| (base_z, sim_chunk)))
.and_then(|base_z| self.sim.get(chunk_pos).map(|sim_chunk| (base_z, sim_chunk))) */
.get_base_z(chunk_pos)
{
Some((base_z, sim_chunk)) => (base_z as i32, sim_chunk),
Some(base_z) => (base_z as i32, self.sim.get(chunk_pos).unwrap()),
// Some((base_z, sim_chunk)) => (base_z as i32, sim_chunk),
None => {
return Ok((
TerrainChunk::new(

1155
world/src/sim/erosion.rs Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,26 @@
use super::WORLD_SIZE;
use bitvec::prelude::{bitbox, bitvec, BitBox};
use common::{terrain::TerrainChunkSize, vol::RectVolSize};
use num::Float;
use rayon::prelude::*;
use std::{f32, f64, u32};
use vek::*;
/// 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 extremes
/// (0 or WORLD_SIZE on one or more axes). It then divides the quantity by cell_size,
/// so the final result is 1 when we are not in a cell along the edge of the world, and
/// ranges between 0 and 1 otherwise (lower when the chunk is closer to the edge).
pub fn map_edge_factor(posi: usize) -> f32 {
uniform_idx_as_vec2(posi)
.map2(WORLD_SIZE.map(|e| e as i32), |e, sz| {
(sz / 2 - (e - sz / 2).abs()) as f32 / 16.0
})
.reduce_partial_min()
.max(0.0)
.min(1.0)
}
/// Computes the cumulative distribution function of the weighted sum of k independent,
/// uniformly distributed random variables between 0 and 1. For each variable i, we use weights[i]
/// as the weight to give samples[i] (the weights should all be positive).
@ -91,7 +110,7 @@ pub fn cdf_irwin_hall<const N: usize>(weights: &[f32; N], samples: [f32; N]) ->
/// generated the index.
///
/// NOTE: Length should always be WORLD_SIZE.x * WORLD_SIZE.y.
pub type InverseCdf = Box<[(f32, f32)]>;
pub type InverseCdf<F = f32> = Box<[(f32, F)]>;
/// Computes the position Vec2 of a SimChunk from an index, where the index was generated by
/// uniform_noise.
@ -135,9 +154,12 @@ pub fn vec2_as_uniform_idx(idx: Vec2<i32>) -> usize {
/// Returns a vec of (f32, f32) pairs consisting of the percentage of chunks with a value lower than
/// this one, and the actual noise value (we don't need to cache it, but it makes ensuring that
/// subsequent code that needs the noise value actually uses the same one we were using here
/// easier).
pub fn uniform_noise(f: impl Fn(usize, Vec2<f64>) -> Option<f32>) -> InverseCdf {
/// easier). Also returns the "inverted index" pointing from a position to a noise.
pub fn uniform_noise<F: Float + Send>(
f: impl Fn(usize, Vec2<f64>) -> Option<F> + Sync,
) -> (InverseCdf<F>, Box<[(usize, F)]>) {
let mut noise = (0..WORLD_SIZE.x * WORLD_SIZE.y)
.into_par_iter()
.filter_map(|i| {
(f(
i,
@ -151,16 +173,130 @@ pub fn uniform_noise(f: impl Fn(usize, Vec2<f64>) -> Option<f32>) -> InverseCdf
// sort_unstable_by is equivalent to sort_by here since we include a unique index in the
// comparison. We could leave out the index, but this might make the order not
// reproduce the same way between different versions of Rust (for example).
noise.sort_unstable_by(|f, g| (f.1, f.0).partial_cmp(&(g.1, g.0)).unwrap());
noise.par_sort_unstable_by(|f, g| (f.1, f.0).partial_cmp(&(g.1, g.0)).unwrap());
// Construct a vector that associates each chunk position with the 1-indexed
// position of the noise in the sorted vector (divided by the vector length).
// This guarantees a uniform distribution among the samples (excluding those that returned
// None, which will remain at zero).
let mut uniform_noise = vec![(0.0, 0.0); WORLD_SIZE.x * WORLD_SIZE.y].into_boxed_slice();
let mut uniform_noise = vec![(0.0, F::zero()); WORLD_SIZE.x * WORLD_SIZE.y].into_boxed_slice();
// NOTE: Consider using try_into here and elsewhere in this function, since i32::MAX
// technically doesn't fit in an f32 (even if we should never reach that limit).
let total = noise.len() as f32;
for (noise_idx, (chunk_idx, noise_val)) in noise.into_iter().enumerate() {
for (noise_idx, &(chunk_idx, noise_val)) in noise.iter().enumerate() {
uniform_noise[chunk_idx] = ((1 + noise_idx) as f32 / total, noise_val);
}
uniform_noise
(uniform_noise, noise.into_boxed_slice())
}
/// Iterate through all cells adjacent and including four chunks whose top-left point is posi.
/// This isn't just the immediate neighbors of a chunk plus the center, because it is designed
/// to cover neighbors of a point in the chunk's "interior."
///
/// This is what's used during cubic interpolation, for example, as it guarantees that for any
/// point between the given chunk (on the top left) and its top-right/down-right/down neighbors,
/// the twelve chunks surrounding this box (its "perimeter") are also inspected.
pub fn local_cells(posi: usize) -> impl Clone + Iterator<Item = usize> {
let pos = uniform_idx_as_vec2(posi);
// NOTE: want to keep this such that the chunk index is in ascending order!
let grid_size = 3i32;
let grid_bounds = 2 * grid_size + 1;
(0..grid_bounds * grid_bounds)
.into_iter()
.map(move |index| {
Vec2::new(
pos.x + (index % grid_bounds) - grid_size,
pos.y + (index / grid_bounds) - grid_size,
)
})
.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)
}
/// 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);
// NOTE: want to keep this such that the chunk index is in ascending order!
[
(-1, -1),
(0, -1),
(1, -1),
(-1, 0),
(1, 0),
(-1, 1),
(0, 1),
(1, 1),
]
.into_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.
pub fn uphill<'a>(dh: &'a [isize], posi: usize) -> impl Clone + Iterator<Item = usize> + 'a {
neighbors(posi).filter(move |&posj| dh[posj] == posi as isize)
}
/// Compute the neighbor "most downhill" from all chunks.
///
/// TODO: See if allocating in advance is worthwhile.
pub fn downhill(h: &[f32], is_ocean: impl Fn(usize) -> bool + Sync) -> Box<[isize]> {
// Constructs not only the list of downhill nodes, but also computes an ordering (visiting
// nodes in order from roots to leaves).
h.par_iter()
.enumerate()
.map(|(posi, &nh)| {
let _pos = uniform_idx_as_vec2(posi);
if is_ocean(posi) {
-2
} else {
let mut best = -1;
let mut besth = nh;
for nposi in neighbors(posi) {
let nbh = h[nposi];
if nbh < besth {
besth = nbh;
best = nposi as isize;
}
}
best
}
})
.collect::<Vec<_>>()
.into_boxed_slice()
}
/// Find all ocean tiles from a height map, using an inductive definition of ocean as one of:
/// - 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 (oldh(posi) <= 0.0).
pub fn get_oceans(oldh: impl Fn(usize) -> f32 + Sync) -> BitBox {
// 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 tiles must be connected to it.
let mut is_ocean = bitbox![0; WORLD_SIZE.x * WORLD_SIZE.y];
let mut stack = Vec::new();
for x in 0..WORLD_SIZE.x as i32 {
stack.push(vec2_as_uniform_idx(Vec2::new(x, 0)));
stack.push(vec2_as_uniform_idx(Vec2::new(x, WORLD_SIZE.y as i32 - 1)));
}
for y in 1..WORLD_SIZE.y as i32 - 1 {
stack.push(vec2_as_uniform_idx(Vec2::new(0, y)));
stack.push(vec2_as_uniform_idx(Vec2::new(WORLD_SIZE.x as i32 - 1, y)));
}
while let Some(chunk_idx) = stack.pop() {
// println!("Ocean chunk {:?}: {:?}", uniform_idx_as_vec2(chunk_idx), oldh(chunk_idx));
if *is_ocean.at(chunk_idx) {
continue;
}
*is_ocean.at(chunk_idx) = true;
stack.extend(neighbors(chunk_idx).filter(|&neighbor_idx| {
// println!("Ocean neighbor: {:?}: {:?}", uniform_idx_as_vec2(neighbor_idx), oldh(neighbor_idx));
oldh(neighbor_idx) <= 0.0
}));
}
is_ocean
}