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" version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" 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]] [[package]]
name = "blake2b_simd" name = "blake2b_simd"
version = "0.5.8" version = "0.5.8"
@ -485,19 +490,21 @@ dependencies = [
[[package]] [[package]]
name = "cocoa" name = "cocoa"
version = "0.14.0" version = "0.18.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"bitflags 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "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)", "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)", "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)", "objc 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]] [[package]]
name = "cocoa" name = "cocoa"
version = "0.18.4" version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"bitflags 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "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" version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index" 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]] [[package]]
name = "core-foundation" name = "core-foundation"
version = "0.6.4" version = "0.6.4"
@ -584,30 +582,11 @@ dependencies = [
"libc 0.2.62 (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-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]] [[package]]
name = "core-foundation-sys" name = "core-foundation-sys"
version = "0.6.2" version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index" 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]] [[package]]
name = "core-graphics" name = "core-graphics"
version = "0.17.3" version = "0.17.3"
@ -1927,14 +1906,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
name = "msgbox" name = "msgbox"
version = "0.2.0" version = "0.4.0"
source = "git+https://github.com/bekker/msgbox-rs.git#d3e12e1cbfcd280bb4de5ad8032bded37d8e573f" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ 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)", "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)", "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.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]] [[package]]
@ -2843,6 +2821,11 @@ dependencies = [
"serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", "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]] [[package]]
name = "rouille" name = "rouille"
version = "3.0.0" version = "3.0.0"
@ -3476,15 +3459,6 @@ dependencies = [
"percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "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]] [[package]]
name = "utf8-ranges" name = "utf8-ranges"
version = "1.0.4" version = "1.0.4"
@ -3532,7 +3506,9 @@ dependencies = [
name = "veloren-client" name = "veloren-client"
version = "0.4.0" version = "0.4.0"
dependencies = [ 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)", "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)", "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)", "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)", "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)", "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)", "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)", "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)", "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)", "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)", "rodio 0.9.0 (git+https://github.com/RustAudio/rodio?rev=e5474a2)",
@ -3656,13 +3632,19 @@ name = "veloren-world"
version = "0.4.0" version = "0.4.0"
dependencies = [ dependencies = [
"arr_macro 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "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)", "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)", "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)", "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)", "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 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)", "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)", "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)", "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)", "vek 0.9.9 (registry+https://github.com/rust-lang/crates.io-index)",
"veloren-common 0.4.0", "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.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 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 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 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 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" "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 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 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 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.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 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_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>" "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 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 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 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 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-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 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-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" "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 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 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 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 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 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" "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 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 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 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 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 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" "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.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 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 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 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 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" "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] [profile.dev]
opt-level = 2 opt-level = 2
overflow-checks = false overflow-checks = false
panic = "abort"
# debug = false
[profile.release] [profile.release]
# panic = "abort"
debug = false debug = false
codegen-units = 1 codegen-units = 1
lto = true lto = true

View File

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

View File

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

View File

@ -20,6 +20,7 @@ use common::{
ChatType, ChatType,
}; };
use hashbrown::HashMap; use hashbrown::HashMap;
use image::DynamicImage;
use log::warn; use log::warn;
use std::{ use std::{
net::SocketAddr, net::SocketAddr,
@ -43,6 +44,7 @@ pub struct Client {
client_state: ClientState, client_state: ClientState,
thread_pool: ThreadPool, thread_pool: ThreadPool,
pub server_info: ServerInfo, pub server_info: ServerInfo,
pub world_map: Arc<DynamicImage>,
postbox: PostBox<ClientMsg, ServerMsg>, postbox: PostBox<ClientMsg, ServerMsg>,
@ -67,11 +69,12 @@ impl Client {
let mut postbox = PostBox::to(addr)?; let mut postbox = PostBox::to(addr)?;
// Wait for initial sync // 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 { Some(ServerMsg::InitialSync {
ecs_state, ecs_state,
entity_uid, entity_uid,
server_info, server_info,
// world_map: /*(map_size, world_map)*/map_size,
}) => { }) => {
// TODO: Voxygen should display this. // TODO: Voxygen should display this.
if server_info.git_hash != common::util::GIT_HASH.to_string() { if server_info.git_hash != common::util::GIT_HASH.to_string() {
@ -87,7 +90,24 @@ impl Client {
.ecs() .ecs()
.entity_from_uid(entity_uid) .entity_from_uid(entity_uid)
.ok_or(Error::ServerWentMad)?; .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)) => { Some(ServerMsg::Error(ServerError::TooManyPlayers)) => {
return Err(Error::TooManyPlayers) return Err(Error::TooManyPlayers)
@ -107,6 +127,7 @@ impl Client {
client_state, client_state,
thread_pool, thread_pool,
server_info, server_info,
world_map,
postbox, postbox,
@ -172,7 +193,7 @@ impl Client {
} }
pub fn set_view_distance(&mut self, view_distance: u32) { 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 self.postbox
.send_message(ClientMsg::SetViewDistance(self.view_distance.unwrap())); .send_message(ClientMsg::SetViewDistance(self.view_distance.unwrap()));
// Can't fail // Can't fail

View File

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

View File

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

View File

@ -98,7 +98,7 @@ impl Server {
let mut state = State::default(); let mut state = State::default();
state state
.ecs_mut() .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 state
.ecs_mut() .ecs_mut()
.add_resource(EventBus::<ServerEvent>::default()); .add_resource(EventBus::<ServerEvent>::default());
@ -867,11 +867,14 @@ impl Server {
client.notify(ServerMsg::Error(ServerError::TooManyPlayers)); client.notify(ServerMsg::Error(ServerError::TooManyPlayers));
} else { } else {
// Return the state of the current world (all of the components that Sphynx tracks). // 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 { client.notify(ServerMsg::InitialSync {
ecs_state: self.state.ecs().gen_state_package(), ecs_state: self.state.ecs().gen_state_package(),
entity_uid: self.state.ecs().uid_from_entity(entity).unwrap().into(), // Can't fail. entity_uid: self.state.ecs().uid_from_entity(entity).unwrap().into(), // Can't fail.
server_info: self.server_info.clone(), 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 }); frontend_events.push(Event::ClientConnected { entity });
} }

View File

@ -50,7 +50,7 @@ serde_derive = "1.0.98"
ron = "0.5.1" ron = "0.5.1"
guillotiere = "0.4.2" guillotiere = "0.4.2"
simplelog = "0.6.0" 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" directories = "2.0.2"
num = "0.2.0" num = "0.2.0"
backtrace = "0.3.33" backtrace = "0.3.33"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,8 +1,6 @@
use noise::{NoiseFn, Seedable, SuperSimplex}; use noise::{Seedable, SuperSimplex};
use rand::thread_rng;
use std::ops::{Add, Mul, Sub};
use vek::*; use vek::*;
use veloren_world::sim::Settlement;
const W: usize = 640; const W: usize = 640;
const H: usize = 640; const H: usize = 640;
@ -10,8 +8,8 @@ const H: usize = 640;
fn main() { fn main() {
let mut win = minifb::Window::new("Turb", W, H, minifb::WindowOptions::default()).unwrap(); let mut win = minifb::Window::new("Turb", W, H, minifb::WindowOptions::default()).unwrap();
let nz_x = SuperSimplex::new().set_seed(0); let _nz_x = SuperSimplex::new().set_seed(0);
let nz_y = SuperSimplex::new().set_seed(1); let _nz_y = SuperSimplex::new().set_seed(1);
let mut time = 0.0f64; let mut time = 0.0f64;
while win.is_open() { 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 { let &ColumnSample {
alt, alt,
chaos, chaos,
water_level: _, water_level,
//river, warp_factor,
surface_color, surface_color,
sub_surface_color, sub_surface_color,
//tree_density, //tree_density,
@ -179,7 +179,7 @@ impl<'a> BlockGen<'a> {
let (_definitely_underground, height, on_cliff, water_height) = let (_definitely_underground, height, on_cliff, water_height) =
if (wposf.z as f32) < alt - 64.0 * chaos { if (wposf.z as f32) < alt - 64.0 * chaos {
// Shortcut warping // Shortcut warping
(true, alt, false, CONFIG.sea_level /*water_level*/) (true, alt, false, water_level)
} else { } else {
// Apply warping // Apply warping
let warp = world let warp = world
@ -189,6 +189,7 @@ impl<'a> BlockGen<'a> {
.get(wposf.div(24.0)) .get(wposf.div(24.0))
.mul((chaos - 0.1).max(0.0).powf(2.0)) .mul((chaos - 0.1).max(0.0).powf(2.0))
.mul(48.0); .mul(48.0);
let warp = Lerp::lerp(0.0, warp, warp_factor);
let surface_height = alt + warp; let surface_height = alt + warp;
@ -221,7 +222,11 @@ impl<'a> BlockGen<'a> {
false, false,
height, height,
on_cliff, 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) .add(1.0)
> 0.9993; > 0.9993;
if cave { if cave && wposf.z as f32 > water_height + 3.0 {
None None
} else { } else {
Some(block) Some(block)
@ -420,13 +425,15 @@ pub struct ZCache<'a> {
impl<'a> ZCache<'a> { impl<'a> ZCache<'a> {
pub fn get_z_limits(&self, block_gen: &mut BlockGen) -> (f32, f32, f32) { pub fn get_z_limits(&self, block_gen: &mut BlockGen) -> (f32, f32, f32) {
let cave_depth = if self.sample.cave_xy.abs() > 0.9 { 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) (self.sample.alt - self.sample.cave_alt + 8.0).max(0.0)
} else { } else {
0.0 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( let cliff = BlockGen::get_cliff_height(
&mut block_gen.column_gen, &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 ground_max = (self.sample.alt + warp + rocks).max(cliff) + 2.0;
let min = min + structure_min; let min = min + structure_min;
let max = (ground_max + structure_max) let max = (ground_max + structure_max).max(self.sample.water_level + 2.0);
.max(self.sample.water_level)
.max(CONFIG.sea_level + 2.0);
// Structures // Structures
let (min, max) = self let (min, max) = self
@ -479,9 +484,7 @@ impl<'a> ZCache<'a> {
}) })
.unwrap_or((min, max)); .unwrap_or((min, max));
let structures_only_min_z = ground_max let structures_only_min_z = ground_max.max(self.sample.water_level + 2.0);
.max(self.sample.water_level)
.max(CONFIG.sea_level + 2.0);
(min, structures_only_min_z, max) (min, structures_only_min_z, max)
} }

View File

@ -2,7 +2,10 @@ use crate::{
all::ForestKind, all::ForestKind,
block::StructureMeta, block::StructureMeta,
generator::{Generator, SpawnRules, TownGen}, 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}, util::{RandomPerm, Sampler, UnitChooser},
CONFIG, CONFIG,
}; };
@ -13,8 +16,10 @@ use common::{
}; };
use lazy_static::lazy_static; use lazy_static::lazy_static;
use noise::NoiseFn; use noise::NoiseFn;
use roots::find_roots_cubic;
use std::{ use std::{
f32, cmp::Reverse,
f32, f64,
ops::{Add, Div, Mul, Neg, Sub}, ops::{Add, Div, Mul, Neg, Sub},
sync::Arc, sync::Arc,
}; };
@ -76,14 +81,15 @@ impl<'a> ColumnGen<'a> {
if seed % 5 == 2 if seed % 5 == 2
&& chunk.temp > CONFIG.desert_temp && chunk.temp > CONFIG.desert_temp
&& chunk.alt > CONFIG.sea_level + 5.0 && chunk.alt > chunk.water_alt + 5.0
&& chunk.chaos <= 0.35 && chunk.chaos <= 0.35
{ {
Some(StructureData { /*Some(StructureData {
pos, pos,
seed, seed,
meta: Some(StructureMeta::Pyramid { height: 140 }), meta: Some(StructureMeta::Pyramid { height: 140 }),
}) })*/
None
} else if seed % 17 == 2 && chunk.chaos < 0.2 { } else if seed % 17 == 2 && chunk.chaos < 0.2 {
Some(StructureData { Some(StructureData {
pos, 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> { 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>>;
@ -134,29 +226,328 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
) * 12.0; ) * 12.0;
let wposf_turb = wposf + turb.map(|e| e as f64); 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 chaos = sim.get_interpolated(wpos, |chunk| chunk.chaos)?;
let temp = sim.get_interpolated(wpos, |chunk| chunk.temp)?; let temp = sim.get_interpolated(wpos, |chunk| chunk.temp)?;
let humidity = sim.get_interpolated(wpos, |chunk| chunk.humidity)?; let humidity = sim.get_interpolated(wpos, |chunk| chunk.humidity)?;
let rockiness = sim.get_interpolated(wpos, |chunk| chunk.rockiness)?; let rockiness = sim.get_interpolated(wpos, |chunk| chunk.rockiness)?;
let tree_density = sim.get_interpolated(wpos, |chunk| chunk.tree_density)?; let tree_density = sim.get_interpolated(wpos, |chunk| chunk.tree_density)?;
let spawn_rate = sim.get_interpolated(wpos, |chunk| chunk.spawn_rate)?; 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 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 let river_width = river_width * (1.0 + river_width_noise * 0.3);
//const RIVER_PROPORTION: f32 = 0.025; // 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),
)),
)
});
/* // Find the average distance to each neighboring body of water.
let river = dryness let mut river_count = 0.0f64;
.abs() let mut overlap_count = 0.0f64;
.neg() let mut river_distance_product = 1.0f64;
.add(RIVER_PROPORTION) let mut river_overlap_distance_product = 0.0f64;
.div(RIVER_PROPORTION) let mut max_river = None;
.max(0.0) let mut max_key = None;
.mul((1.0 - (chaos - 0.15) * 20.0).max(0.0).min(1.0)); // IDEA:
*/ // For every "nearby" chunk, check whether it is a river. If so, find the closest point on
let river = 0.0; // 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 let cliff_hill = (sim
.gen_ctx .gen_ctx
@ -164,14 +555,13 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
.get((wposf_turb.div(128.0)).into_array()) as f32) .get((wposf_turb.div(128.0)).into_array()) as f32)
.mul(24.0); .mul(24.0);
let riverless_alt = sim.get_interpolated(wpos, |chunk| chunk.alt)? let riverless_alt_delta = (sim
+ (sim
.gen_ctx .gen_ctx
.small_nz .small_nz
.get((wposf_turb.div(200.0)).into_array()) as f32) .get((wposf_turb.div(200.0)).into_array()) as f32)
.abs() .abs()
.mul(chaos.max(0.05)) .mul(chaos.max(0.05))
.mul(55.0) .mul(27.0)
+ (sim + (sim
.gen_ctx .gen_ctx
.small_nz .small_nz
@ -179,20 +569,239 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
.abs() .abs()
.mul((1.0 - chaos).max(0.3)) .mul((1.0 - chaos).max(0.3))
.mul(1.0 - humidity) .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 is_cliffs = sim_chunk.is_cliffs;
let near_cliffs = sim_chunk.near_cliffs; let near_cliffs = sim_chunk.near_cliffs;
let alt = riverless_alt let river_gouge = 0.5;
- (1.0 - river) let (in_water, alt_, water_level, warp_factor) = if let Some((
.mul(f32::consts::PI) max_border_river_pos,
.cos() river_chunk,
.add(1.0) max_border_river,
.mul(0.5) max_border_river_dist,
.mul(24.0); )) = 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( let rock = (sim.gen_ctx.small_nz.get(
Vec3::new(wposf.x, wposf.y, alt as f64) 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), .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 // Caves
let cave_at = |wposf: Vec2<f64>| { let cave_at = |wposf: Vec2<f64>| {
(sim.gen_ctx.cave_0_nz.get( (sim.gen_ctx.cave_0_nz.get(
@ -470,34 +1017,33 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
.powf(15.0) .powf(15.0)
.mul(150.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 { Some(ColumnSample {
alt, alt,
chaos, chaos,
water_level, water_level,
river, warp_factor,
surface_color: Rgb::lerp( surface_color: Rgb::lerp(
sand, sand,
// Land // Land
Rgb::lerp(
ground, 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),
),
// Beach // Beach
((alt - CONFIG.sea_level - 1.0) / 2.0) ((ocean_level - 1.0) / 2.0).max(0.0),
.min(1.0 - river * 2.0)
.max(0.0),
), ),
sub_surface_color: dirt, sub_surface_color: dirt,
tree_density, tree_density,
@ -523,7 +1069,11 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
.town .town
.as_ref() .as_ref()
.map(|town| TownGen.spawn_rules(town, wpos)) .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 alt: f32,
pub chaos: f32, pub chaos: f32,
pub water_level: f32, pub water_level: f32,
pub river: f32, pub warp_factor: f32,
pub surface_color: Rgb<f32>, pub surface_color: Rgb<f32>,
pub sub_surface_color: Rgb<f32>, pub sub_surface_color: Rgb<f32>,
pub tree_density: f32, pub tree_density: f32,

View File

@ -7,15 +7,54 @@ pub struct Config {
pub desert_hum: f32, pub desert_hum: f32,
pub forest_hum: f32, pub forest_hum: f32,
pub jungle_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 { pub const CONFIG: Config = Config {
sea_level: 140.0, sea_level: 140.0,
mountain_scale: 1000.0, mountain_scale: 2048.0,
snow_temp: -0.6, snow_temp: -0.6,
tropical_temp: 0.2, tropical_temp: 0.2,
desert_temp: 0.6, desert_temp: 0.6,
desert_hum: 0.15, desert_hum: 0.15,
forest_hum: 0.5, forest_hum: 0.5,
jungle_hum: 0.85, 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 stone = Block::new(BlockKind::Dense, Rgb::new(200, 220, 255));
let water = Block::new(BlockKind::Water, Rgb::new(60, 90, 190)); 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 let (base_z, sim_chunk) = match self
.sim .sim
.get_interpolated( /*.get_interpolated(
chunk_pos.map2(chunk_size2d, |e, sz: u32| e * sz as i32 + sz as i32 / 2), chunk_pos.map2(chunk_size2d, |e, sz: u32| e * sz as i32 + sz as i32 / 2),
|chunk| chunk.get_base_z(), |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 => { None => {
return Ok(( return Ok((
TerrainChunk::new( 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 super::WORLD_SIZE;
use bitvec::prelude::{bitbox, bitvec, BitBox};
use common::{terrain::TerrainChunkSize, vol::RectVolSize}; use common::{terrain::TerrainChunkSize, vol::RectVolSize};
use num::Float;
use rayon::prelude::*;
use std::{f32, f64, u32};
use vek::*; 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, /// 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] /// 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). /// 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. /// generated the index.
/// ///
/// NOTE: Length should always be WORLD_SIZE.x * WORLD_SIZE.y. /// 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 /// Computes the position Vec2 of a SimChunk from an index, where the index was generated by
/// uniform_noise. /// 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 /// 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 /// 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 /// subsequent code that needs the noise value actually uses the same one we were using here
/// easier). /// easier). Also returns the "inverted index" pointing from a position to a noise.
pub fn uniform_noise(f: impl Fn(usize, Vec2<f64>) -> Option<f32>) -> InverseCdf { 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) let mut noise = (0..WORLD_SIZE.x * WORLD_SIZE.y)
.into_par_iter()
.filter_map(|i| { .filter_map(|i| {
(f( (f(
i, 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 // 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 // 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). // 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 // 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). // 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 // This guarantees a uniform distribution among the samples (excluding those that returned
// None, which will remain at zero). // 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; 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[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
} }