mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge branch 'ffp/world-colors' into 'master'
Ffp/world colors See merge request veloren/veloren!740
This commit is contained in:
commit
d496080d84
1
.gitattributes
vendored
1
.gitattributes
vendored
@ -4,3 +4,4 @@
|
||||
*.wav filter=lfs diff=lfs merge=lfs -text
|
||||
*.ogg filter=lfs diff=lfs merge=lfs -text
|
||||
*.ico filter=lfs diff=lfs merge=lfs -text
|
||||
assets/world/map/*.bin filter=lfs diff=lfs merge=lfs -text
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -33,6 +33,7 @@
|
||||
*.log
|
||||
settings.ron
|
||||
run.sh
|
||||
maps
|
||||
screenshots
|
||||
todo.txt
|
||||
|
||||
|
@ -16,6 +16,8 @@ before_script:
|
||||
- free -h
|
||||
- cargo --version
|
||||
- export DISABLE_GIT_LFS_CHECK=true
|
||||
- export VELOREN_ASSETS="$(pwd)/assets"
|
||||
- echo "VELOREN_ASSETS=$VELOREN_ASSETS"
|
||||
- rm -r target || echo "target doesnt exist, which is fine"
|
||||
- ln -s /dockercache/veloren/target target
|
||||
|
||||
@ -32,7 +34,7 @@ before_script:
|
||||
optional-release:linux:
|
||||
<<: *optional-release
|
||||
script:
|
||||
- VELOREN_ASSETS=assets cargo build --verbose --release
|
||||
- cargo build --verbose --release
|
||||
- cp target/release/veloren-server-cli $CI_PROJECT_DIR
|
||||
- cp target/release/veloren-voxygen $CI_PROJECT_DIR
|
||||
- strip --strip-all veloren-server-cli
|
||||
@ -48,7 +50,7 @@ optional-release:linux:
|
||||
optional-release:windows:
|
||||
<<: *optional-release
|
||||
script:
|
||||
- VELOREN_ASSETS=assets cargo build --verbose --target=x86_64-pc-windows-gnu --release
|
||||
- cargo build --verbose --target=x86_64-pc-windows-gnu --release
|
||||
- cp target/x86_64-pc-windows-gnu/release/veloren-server-cli.exe $CI_PROJECT_DIR
|
||||
- cp target/x86_64-pc-windows-gnu/release/veloren-voxygen.exe $CI_PROJECT_DIR
|
||||
artifacts:
|
||||
@ -117,6 +119,7 @@ benchmarks:
|
||||
tags:
|
||||
- veloren-docker
|
||||
script:
|
||||
- unset DISABLE_GIT_LFS_CHECK
|
||||
- cargo bench
|
||||
allow_failure: true
|
||||
|
||||
@ -132,7 +135,7 @@ linux:
|
||||
tags:
|
||||
- veloren-docker
|
||||
script:
|
||||
- VELOREN_ASSETS=assets cargo build --release
|
||||
- cargo build --release
|
||||
- cp -r target/release/veloren-server-cli $CI_PROJECT_DIR
|
||||
- cp -r target/release/veloren-voxygen $CI_PROJECT_DIR
|
||||
- strip --strip-all veloren-server-cli
|
||||
@ -157,7 +160,7 @@ windows:
|
||||
tags:
|
||||
- veloren-docker
|
||||
script:
|
||||
- VELOREN_ASSETS=assets cargo build --target=x86_64-pc-windows-gnu --release
|
||||
- cargo build --target=x86_64-pc-windows-gnu --release
|
||||
- cp -r target/x86_64-pc-windows-gnu/release/veloren-server-cli.exe $CI_PROJECT_DIR
|
||||
- cp -r target/x86_64-pc-windows-gnu/release/veloren-voxygen.exe $CI_PROJECT_DIR
|
||||
artifacts:
|
||||
|
15
CHANGELOG.md
15
CHANGELOG.md
@ -33,6 +33,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Added a localization system to provide multi-language support
|
||||
to voxygen
|
||||
- Added French language for Voxygen
|
||||
- Added rivers and lakes which follow realistic physical paths.
|
||||
- Added a sophisticated erosion system for world generation which
|
||||
dramatically changes the world layout.
|
||||
- Added tracking of sediment vs. bedrock, which is visually reflected in the
|
||||
world.
|
||||
- Added map saving and loading for altitude and bedrock, with built in
|
||||
versioning for forwards compatibility.
|
||||
- Added a default map, which is used to speed up starting single player.
|
||||
- Added a 3D renderered map, which is also used by the server to send the map
|
||||
to the client.
|
||||
|
||||
### Changed
|
||||
|
||||
@ -66,6 +76,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Terrain meshing optimized further
|
||||
- Tree leaves no longer color blended
|
||||
- Actual character stats displayed in character window
|
||||
- Made significant changes to the noise functions used for world generation.
|
||||
- Improved colors during world generation.
|
||||
- Significantly reduced the use of warp during world generation.
|
||||
- Parallelized and otherwise sped up significant parts of world generation.
|
||||
- Various performance improvements to world generation.
|
||||
|
||||
### Removed
|
||||
|
||||
|
40
Cargo.lock
generated
40
Cargo.lock
generated
@ -604,7 +604,7 @@ dependencies = [
|
||||
"rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand_os 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand_xoshiro 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rayon 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rayon 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_derive 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_json 1.0.42 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@ -1342,6 +1342,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"ahash 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rayon 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
@ -1359,7 +1360,7 @@ version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"atom 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rayon 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rayon 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1472,7 +1473,7 @@ version = "0.1.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rayon 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rayon 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1986,6 +1987,14 @@ dependencies = [
|
||||
"stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "packed_simd"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pango"
|
||||
version = "0.4.0"
|
||||
@ -2452,17 +2461,17 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rayon"
|
||||
version = "1.2.1"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"crossbeam-deque 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rayon-core 1.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rayon-core 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rayon-core"
|
||||
version = "1.6.1"
|
||||
version = "1.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"crossbeam-deque 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@ -2761,7 +2770,7 @@ dependencies = [
|
||||
"arrayvec 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"hashbrown 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"mopa 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rayon 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rayon 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"shred-derive 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"smallvec 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
@ -2864,7 +2873,7 @@ dependencies = [
|
||||
"hashbrown 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"hibitset 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rayon 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rayon 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"shred 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"shrev 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@ -3197,7 +3206,7 @@ dependencies = [
|
||||
"notify 5.0.0-pre.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"parking_lot 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rayon 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rayon 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ron 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_derive 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@ -3297,20 +3306,26 @@ name = "veloren-world"
|
||||
version = "0.4.0"
|
||||
dependencies = [
|
||||
"arr_macro 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"bincode 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"bitvec 0.15.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"hashbrown 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"image 0.22.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"itertools 0.8.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)",
|
||||
"minifb 0.13.0 (git+https://github.com/emoon/rust_minifb.git)",
|
||||
"noise 0.6.0 (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)",
|
||||
"packed_simd 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"pretty_env_logger 0.3.1 (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.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rayon 1.3.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.102 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_derive 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"vek 0.9.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"veloren-common 0.4.0",
|
||||
]
|
||||
@ -3793,6 +3808,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
"checksum ordered-float 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "18869315e81473c951eb56ad5558bbc56978562d3ecfb87abb7a1e944cea4518"
|
||||
"checksum osmesa-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "88cfece6e95d2e717e0872a7f53a8684712ad13822a7979bc760b9c77ec0013b"
|
||||
"checksum owning_ref 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "cdf84f41639e037b484f93433aa3897863b561ed65c6e59c7073d7c561710f37"
|
||||
"checksum packed_simd 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a85ea9fc0d4ac0deb6fe7911d38786b32fc11119afd9e9d38b84ff691ce64220"
|
||||
"checksum pango 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "45374801e224373c3c0393cd48073c81093494c8735721e81d1dbaa4096b2767"
|
||||
"checksum pango-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "94039b3921a4af4058a3e4335e5d15099101f298a92f5afc40bab3a3027594a1"
|
||||
"checksum parking_lot 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "149d8f5b97f3c1133e3cfcd8886449959e856b557ff281e292b733d7c69e005e"
|
||||
@ -3845,8 +3861,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
"checksum rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c"
|
||||
"checksum rand_xoshiro 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0e18c91676f670f6f0312764c759405f13afb98d5d73819840cf72a518487bff"
|
||||
"checksum raw-window-handle 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9db80d08d3ed847ce4fb3def46de0af4bfb6155bd09bd6eaf28b5ac72541c1f1"
|
||||
"checksum rayon 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "43739f8831493b276363637423d3622d4bd6394ab6f0a9c4a552e208aeb7fddd"
|
||||
"checksum rayon-core 1.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f8bf17de6f23b05473c437eb958b9c850bfc8af0961fe17b4cc92d5a627b4791"
|
||||
"checksum rayon 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "db6ce3297f9c85e16621bb8cca38a06779ffc31bb8184e1be4bed2be4678a098"
|
||||
"checksum rayon-core 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "08a89b46efaf957e52b18062fb2f4660f8b8a4dde1807ca002690868ef2c85a9"
|
||||
"checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2"
|
||||
"checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84"
|
||||
"checksum redox_users 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4ecedbca3bf205f8d8f5c2b44d83cd0690e39ee84b951ed649e9f1841132b66d"
|
||||
|
10
Cargo.toml
10
Cargo.toml
@ -39,11 +39,21 @@ opt-level = 2
|
||||
[profile.dev.package."veloren-world"]
|
||||
opt-level = 2
|
||||
|
||||
[profile.no_overflow.package."veloren-world"]
|
||||
opt-level = 3
|
||||
|
||||
# this profile is used by developers if dev doesn't has enough debug information, the name must != debug, as debug is used by dev because....
|
||||
[profile.debuginfo]
|
||||
inherits= 'dev'
|
||||
debug = true
|
||||
|
||||
# used to perform things that do a *lot* of math (i.e. worldgen) but still need reasonable compilation time. Ideally this would also
|
||||
# add -C target-cpu=native, but I don't think you can set this by profile currently.
|
||||
[profile.no_overflow]
|
||||
inherits= 'dev'
|
||||
overflow-checks = false
|
||||
debug-assertions = false
|
||||
|
||||
# this profile is used for veloren releases, compile time doesn't matter
|
||||
# we need stacktraces, light debug information, as much checks as possible
|
||||
# I would like to put it in a seperate `official_release` target, but that doesnt share caches with `cargo test` and `cargo bench`
|
||||
|
BIN
assets/voxygen/background/map.png
(Stored with Git LFS)
BIN
assets/voxygen/background/map.png
(Stored with Git LFS)
Binary file not shown.
52
assets/world/manifests/birch.ron
Normal file
52
assets/world/manifests/birch.ron
Normal file
@ -0,0 +1,52 @@
|
||||
(
|
||||
[
|
||||
(
|
||||
specifier: "world.tree.birch.1",
|
||||
center: (12, 9, 10)
|
||||
),
|
||||
(
|
||||
specifier: "world.tree.birch.2",
|
||||
center: (12, 10, 10)
|
||||
),
|
||||
(
|
||||
specifier: "world.tree.birch.3",
|
||||
center: (9, 10, 10)
|
||||
),
|
||||
(
|
||||
specifier: "world.tree.birch.4",
|
||||
center: (9, 10, 10)
|
||||
),
|
||||
(
|
||||
specifier: "world.tree.birch.5",
|
||||
center: (9, 10, 10)
|
||||
),
|
||||
(
|
||||
specifier: "world.tree.birch.6",
|
||||
center: (9, 9, 10)
|
||||
),
|
||||
(
|
||||
specifier: "world.tree.birch.7",
|
||||
center: (10, 10, 10)
|
||||
),
|
||||
(
|
||||
specifier: "world.tree.birch.8",
|
||||
center: (9, 9, 10)
|
||||
),
|
||||
(
|
||||
specifier: "world.tree.birch.9",
|
||||
center: (9, 10, 10)
|
||||
),
|
||||
(
|
||||
specifier: "world.tree.birch.10",
|
||||
center: (9, 10, 10)
|
||||
),
|
||||
(
|
||||
specifier: "world.tree.birch.11",
|
||||
center: (9, 10, 10)
|
||||
),
|
||||
(
|
||||
specifier: "world.tree.birch.12",
|
||||
center: (9, 10, 10)
|
||||
),
|
||||
]
|
||||
)
|
BIN
assets/world/map/veloren_0_5_0_0.bin
(Stored with Git LFS)
Normal file
BIN
assets/world/map/veloren_0_5_0_0.bin
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/world/tree/acacia_2/1.vox
(Stored with Git LFS)
Normal file
BIN
assets/world/tree/acacia_2/1.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/world/tree/acacia_2/2.vox
(Stored with Git LFS)
Normal file
BIN
assets/world/tree/acacia_2/2.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/world/tree/acacia_2/3.vox
(Stored with Git LFS)
Normal file
BIN
assets/world/tree/acacia_2/3.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/world/tree/acacia_2/4.vox
(Stored with Git LFS)
Normal file
BIN
assets/world/tree/acacia_2/4.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/world/tree/acacia_2/5.vox
(Stored with Git LFS)
Normal file
BIN
assets/world/tree/acacia_2/5.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/world/tree/birch/1.vox
(Stored with Git LFS)
BIN
assets/world/tree/birch/1.vox
(Stored with Git LFS)
Binary file not shown.
BIN
assets/world/tree/birch/10.vox
(Stored with Git LFS)
BIN
assets/world/tree/birch/10.vox
(Stored with Git LFS)
Binary file not shown.
BIN
assets/world/tree/birch/11.vox
(Stored with Git LFS)
BIN
assets/world/tree/birch/11.vox
(Stored with Git LFS)
Binary file not shown.
BIN
assets/world/tree/birch/12.vox
(Stored with Git LFS)
BIN
assets/world/tree/birch/12.vox
(Stored with Git LFS)
Binary file not shown.
BIN
assets/world/tree/birch/2.vox
(Stored with Git LFS)
BIN
assets/world/tree/birch/2.vox
(Stored with Git LFS)
Binary file not shown.
BIN
assets/world/tree/birch/3.vox
(Stored with Git LFS)
BIN
assets/world/tree/birch/3.vox
(Stored with Git LFS)
Binary file not shown.
BIN
assets/world/tree/birch/4.vox
(Stored with Git LFS)
BIN
assets/world/tree/birch/4.vox
(Stored with Git LFS)
Binary file not shown.
BIN
assets/world/tree/birch/5.vox
(Stored with Git LFS)
BIN
assets/world/tree/birch/5.vox
(Stored with Git LFS)
Binary file not shown.
BIN
assets/world/tree/birch/6.vox
(Stored with Git LFS)
BIN
assets/world/tree/birch/6.vox
(Stored with Git LFS)
Binary file not shown.
BIN
assets/world/tree/birch/7.vox
(Stored with Git LFS)
BIN
assets/world/tree/birch/7.vox
(Stored with Git LFS)
Binary file not shown.
BIN
assets/world/tree/birch/8.vox
(Stored with Git LFS)
BIN
assets/world/tree/birch/8.vox
(Stored with Git LFS)
Binary file not shown.
BIN
assets/world/tree/birch/9.vox
(Stored with Git LFS)
BIN
assets/world/tree/birch/9.vox
(Stored with Git LFS)
Binary file not shown.
@ -14,4 +14,4 @@ num_cpus = "1.10.1"
|
||||
log = "0.4.8"
|
||||
specs = "0.15.1"
|
||||
vek = { version = "0.9.9", features = ["serde"] }
|
||||
hashbrown = { version = "0.6.2", features = ["serde", "nightly"] }
|
||||
hashbrown = { version = "0.6.2", features = ["rayon", "serde", "nightly"] }
|
||||
|
@ -11,6 +11,7 @@ pub use specs::{
|
||||
Builder, DispatcherBuilder, Entity as EcsEntity, ReadStorage, WorldExt,
|
||||
};
|
||||
|
||||
use byteorder::{ByteOrder, LittleEndian};
|
||||
use common::{
|
||||
comp::{self, ControlEvent, Controller, ControllerInputs, InventoryManip},
|
||||
msg::{
|
||||
@ -55,7 +56,7 @@ pub struct Client {
|
||||
client_state: ClientState,
|
||||
thread_pool: ThreadPool,
|
||||
pub server_info: ServerInfo,
|
||||
pub world_map: Arc<DynamicImage>,
|
||||
pub world_map: (Arc<DynamicImage>, Vec2<u32>),
|
||||
pub player_list: HashMap<u64, String>,
|
||||
|
||||
postbox: PostBox<ClientMsg, ServerMsg>,
|
||||
@ -87,7 +88,7 @@ impl Client {
|
||||
entity_package,
|
||||
server_info,
|
||||
time_of_day,
|
||||
// world_map: /*(map_size, world_map)*/map_size,
|
||||
world_map: (map_size, world_map),
|
||||
}) => {
|
||||
// TODO: Display that versions don't match in Voxygen
|
||||
if server_info.git_hash != common::util::GIT_HASH.to_string() {
|
||||
@ -105,23 +106,24 @@ impl Client {
|
||||
let entity = state.ecs_mut().apply_entity_package(entity_package);
|
||||
*state.ecs_mut().write_resource() = time_of_day;
|
||||
|
||||
// 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);
|
||||
assert_eq!(world_map.len(), (map_size.x * map_size.y) as usize);
|
||||
let mut 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::debug!("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()))?
|
||||
}));
|
||||
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, map_size.y, world_map_raw);
|
||||
world_map.ok_or(Error::Other("Server sent a bad world map image".into()))?
|
||||
})
|
||||
// Flip the image, since Voxygen uses an orientation where rotation from
|
||||
// positive x axis to positive y axis is counterclockwise around the z axis.
|
||||
.flipv(),
|
||||
);
|
||||
log::debug!("Done preparing image...");
|
||||
|
||||
(state, entity, server_info, world_map)
|
||||
(state, entity, server_info, (world_map, map_size))
|
||||
}
|
||||
Some(ServerMsg::Error(ServerError::TooManyPlayers)) => {
|
||||
return Err(Error::TooManyPlayers)
|
||||
|
@ -20,10 +20,10 @@ ron = "0.5.1"
|
||||
bincode = "1.2.0"
|
||||
log = "0.4.8"
|
||||
rand = "0.7.2"
|
||||
rayon = "1.2.0"
|
||||
rayon = "^1.3.0"
|
||||
lazy_static = "1.4.0"
|
||||
lz4-compress = "0.1.1"
|
||||
hashbrown = { version = "0.6.2", features = ["serde", "nightly"] }
|
||||
hashbrown = { version = "0.6.2", features = ["rayon", "serde", "nightly"] }
|
||||
find_folder = "0.3.0"
|
||||
parking_lot = "0.9.0"
|
||||
crossbeam = "=0.7.2"
|
||||
|
@ -37,7 +37,7 @@ pub enum ServerMsg {
|
||||
entity_package: sync::EntityPackage<EcsCompPacket>,
|
||||
server_info: ServerInfo,
|
||||
time_of_day: state::TimeOfDay,
|
||||
// world_map: Vec2<usize>, /*, Vec<u32>)*/
|
||||
world_map: (Vec2<u32>, Vec<u32>),
|
||||
},
|
||||
PlayerListUpdate(PlayerListUpdate),
|
||||
StateAnswer(Result<ClientState, (RequestStateError, ClientState)>),
|
||||
|
@ -67,7 +67,7 @@ const SEND_TOK: Token = Token(3);
|
||||
const RECV_TOK: Token = Token(4);
|
||||
const MIDDLEMAN_TOK: Token = Token(5);
|
||||
|
||||
const MAX_MSG_BYTES: usize = 1 << 20;
|
||||
const MAX_MSG_BYTES: usize = 1 << 28;
|
||||
|
||||
enum CtrlMsg {
|
||||
Shutdown,
|
||||
|
@ -43,7 +43,7 @@ impl From<channel::TryRecvError> for Error {
|
||||
|
||||
pub trait PostMsg = Serialize + DeserializeOwned + 'static + Send;
|
||||
|
||||
const MAX_MSG_SIZE: usize = 1 << 20;
|
||||
const MAX_MSG_SIZE: usize = 1 << 28;
|
||||
|
||||
pub struct PostOffice<S: PostMsg, R: PostMsg> {
|
||||
listener: TcpListener,
|
||||
|
@ -26,6 +26,7 @@ impl<ChonkSize: RectVolSize> VolSize for SubChunkSize<ChonkSize> {
|
||||
const SIZE: Vec3<u32> = Vec3 {
|
||||
x: ChonkSize::RECT_SIZE.x,
|
||||
y: ChonkSize::RECT_SIZE.x,
|
||||
// NOTE: Currently, use 32 instead of 2 for RECT_SIZE.x = 128.
|
||||
z: ChonkSize::RECT_SIZE.x / 2,
|
||||
};
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ serde = "1.0.102"
|
||||
serde_derive = "1.0.102"
|
||||
rand = "0.7.2"
|
||||
chrono = "0.4.9"
|
||||
hashbrown = { version = "0.6.2", features = ["serde", "nightly"] }
|
||||
hashbrown = { version = "0.6.2", features = ["rayon", "serde", "nightly"] }
|
||||
crossbeam = "=0.7.2"
|
||||
prometheus = "0.7"
|
||||
prometheus-static-metric = "0.2"
|
||||
|
@ -990,6 +990,7 @@ fn handle_debug_column(server: &mut Server, entity: EcsEntity, args: String, act
|
||||
let foo = || {
|
||||
// let sim_chunk = sim.get(chunk_pos)?;
|
||||
let alt = sim.get_interpolated(wpos, |chunk| chunk.alt)?;
|
||||
let basement = sim.get_interpolated(wpos, |chunk| chunk.basement)?;
|
||||
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)?;
|
||||
@ -1008,6 +1009,7 @@ fn handle_debug_column(server: &mut Server, entity: EcsEntity, args: String, act
|
||||
r#"wpos: {:?}
|
||||
alt {:?} ({:?})
|
||||
water_alt {:?} ({:?})
|
||||
basement {:?}
|
||||
river {:?}
|
||||
downhill {:?}
|
||||
chaos {:?}
|
||||
@ -1022,6 +1024,7 @@ spawn_rate {:?} "#,
|
||||
col.alt,
|
||||
water_alt,
|
||||
col.water_level,
|
||||
basement,
|
||||
river,
|
||||
downhill,
|
||||
chaos,
|
||||
|
@ -46,7 +46,10 @@ use std::{
|
||||
};
|
||||
use uvth::{ThreadPool, ThreadPoolBuilder};
|
||||
use vek::*;
|
||||
use world::{sim::WORLD_SIZE, World};
|
||||
use world::{
|
||||
sim::{FileOpts, WorldOpts, DEFAULT_WORLD_MAP, WORLD_SIZE},
|
||||
World,
|
||||
};
|
||||
const CLIENT_TIMEOUT: f64 = 20.0; // Seconds
|
||||
|
||||
pub enum Event {
|
||||
@ -73,6 +76,7 @@ pub struct Tick(u64);
|
||||
pub struct Server {
|
||||
state: State,
|
||||
world: Arc<World>,
|
||||
map: Vec<u32>,
|
||||
|
||||
postoffice: PostOffice<ServerMsg, ClientMsg>,
|
||||
|
||||
@ -104,7 +108,20 @@ impl Server {
|
||||
state.ecs_mut().register::<RegionSubscription>();
|
||||
state.ecs_mut().register::<Client>();
|
||||
|
||||
let world = World::generate(settings.world_seed);
|
||||
let world = World::generate(
|
||||
settings.world_seed,
|
||||
WorldOpts {
|
||||
seed_elements: true,
|
||||
world_file: if let Some(ref opts) = settings.map_file {
|
||||
opts.clone()
|
||||
} else {
|
||||
// Load default map from assets.
|
||||
FileOpts::LoadAsset(DEFAULT_WORLD_MAP.into())
|
||||
},
|
||||
..WorldOpts::default()
|
||||
},
|
||||
);
|
||||
let map = world.sim().get_map();
|
||||
|
||||
let spawn_point = {
|
||||
// NOTE: all of these `.map(|e| e as [type])` calls should compile into no-ops,
|
||||
@ -164,6 +181,7 @@ impl Server {
|
||||
let this = Self {
|
||||
state,
|
||||
world: Arc::new(world),
|
||||
map,
|
||||
|
||||
postoffice: PostOffice::bind(settings.gameserver_address)?,
|
||||
|
||||
@ -1070,7 +1088,7 @@ impl Server {
|
||||
.create_entity_package(entity),
|
||||
server_info: self.server_info.clone(),
|
||||
time_of_day: *self.state.ecs().read_resource(),
|
||||
// world_map: (WORLD_SIZE/*, self.world.sim().get_map()*/),
|
||||
world_map: (WORLD_SIZE.map(|e| e as u32), self.map.clone()),
|
||||
});
|
||||
log::debug!("Done initial sync with client.");
|
||||
|
||||
|
@ -1,6 +1,9 @@
|
||||
use portpicker::pick_unused_port;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::{fs, io::prelude::*, net::SocketAddr, path::PathBuf};
|
||||
use world::sim::FileOpts;
|
||||
|
||||
const DEFAULT_WORLD_SEED: u32 = 5284;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[serde(default)]
|
||||
@ -15,6 +18,9 @@ pub struct ServerSettings {
|
||||
//pub login_server: whatever
|
||||
pub start_time: f64,
|
||||
pub admins: Vec<String>,
|
||||
/// When set to None, loads the default map file (if available); otherwise, uses the value of
|
||||
/// the file options to decide how to proceed.
|
||||
pub map_file: Option<FileOpts>,
|
||||
}
|
||||
|
||||
impl Default for ServerSettings {
|
||||
@ -22,11 +28,12 @@ impl Default for ServerSettings {
|
||||
Self {
|
||||
gameserver_address: SocketAddr::from(([0; 4], 14004)),
|
||||
metrics_address: SocketAddr::from(([0; 4], 14005)),
|
||||
world_seed: 5284,
|
||||
world_seed: DEFAULT_WORLD_SEED,
|
||||
server_name: "Veloren Alpha".to_owned(),
|
||||
server_description: "This is the best Veloren server.".to_owned(),
|
||||
max_players: 100,
|
||||
start_time: 9.0 * 3600.0,
|
||||
map_file: None,
|
||||
admins: [
|
||||
"Pfau",
|
||||
"zesterer",
|
||||
@ -87,6 +94,7 @@ impl ServerSettings {
|
||||
}
|
||||
|
||||
pub fn singleplayer() -> Self {
|
||||
let load = Self::load();
|
||||
Self {
|
||||
//BUG: theoretically another process can grab the port between here and server creation, however the timewindow is quite small
|
||||
gameserver_address: SocketAddr::from((
|
||||
@ -97,12 +105,18 @@ impl ServerSettings {
|
||||
[127, 0, 0, 1],
|
||||
pick_unused_port().expect("Failed to find unused port!"),
|
||||
)),
|
||||
world_seed: 1337,
|
||||
// If loading the default map file, make sure the seed is also default.
|
||||
world_seed: if load.map_file.is_some() {
|
||||
load.world_seed
|
||||
} else {
|
||||
DEFAULT_WORLD_SEED
|
||||
},
|
||||
server_name: "Singleplayer".to_owned(),
|
||||
server_description: "Who needs friends anyway?".to_owned(),
|
||||
max_players: 100,
|
||||
start_time: 9.0 * 3600.0,
|
||||
admins: vec!["singleplayer".to_string()], // TODO: Let the player choose if they want to use admin commands or not
|
||||
..load // Fill in remaining fields from settings.ron.
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -59,7 +59,7 @@ treeculler = { git = "https://gitlab.com/yusdacra/treeculler.git" }
|
||||
rodio = { git = "https://github.com/RustAudio/rodio", rev = "e5474a2"}
|
||||
cpal = "0.10"
|
||||
crossbeam = "=0.7.2"
|
||||
hashbrown = { version = "0.6.2", features = ["serde", "nightly"] }
|
||||
hashbrown = { version = "0.6.2", features = ["rayon", "serde", "nightly"] }
|
||||
chrono = "0.4.9"
|
||||
rust-argon2 = "0.5"
|
||||
bincode = "1.2"
|
||||
|
@ -6,7 +6,7 @@ use criterion::{black_box, criterion_group, criterion_main, Benchmark, Criterion
|
||||
use std::sync::Arc;
|
||||
use vek::*;
|
||||
use veloren_voxygen::mesh::Meshable;
|
||||
use world::World;
|
||||
use world::{sim, World};
|
||||
|
||||
const CENTER: Vec2<i32> = Vec2 { x: 512, y: 512 };
|
||||
const GEN_SIZE: i32 = 4;
|
||||
@ -14,7 +14,17 @@ const GEN_SIZE: i32 = 4;
|
||||
pub fn criterion_benchmark(c: &mut Criterion) {
|
||||
// Generate chunks here to test
|
||||
let mut terrain = TerrainGrid::new().unwrap();
|
||||
let world = World::generate(42);
|
||||
let world = World::generate(
|
||||
42,
|
||||
sim::WorldOpts {
|
||||
// NOTE: If this gets too expensive, we can turn it off.
|
||||
// TODO: Consider an option to turn off all erosion as well, or even provide altitude
|
||||
// directly with a closure.
|
||||
seed_elements: true,
|
||||
world_file: sim::FileOpts::LoadAsset(sim::DEFAULT_WORLD_MAP.into()),
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
(0..GEN_SIZE)
|
||||
.flat_map(|x| (0..GEN_SIZE).map(move |y| Vec2::new(x, y)))
|
||||
.map(|offset| offset + CENTER)
|
||||
|
@ -254,7 +254,6 @@ image_ids! {
|
||||
help:"voxygen.element.help",
|
||||
|
||||
charwindow_gradient:"voxygen.element.misc_bg.charwindow",
|
||||
map_placeholder: "voxygen.background.map",
|
||||
|
||||
death_bg: "voxygen.background.death",
|
||||
hurt_bg: "voxygen.background.hurt",
|
||||
|
@ -1,6 +1,6 @@
|
||||
use super::{img_ids::Imgs, Fonts, Show, TEXT_COLOR};
|
||||
use client::{self, Client};
|
||||
use common::comp;
|
||||
use common::{comp, terrain::TerrainChunkSize, vol::RectVolSize};
|
||||
use conrod_core::{
|
||||
color,
|
||||
image::Id,
|
||||
@ -30,7 +30,7 @@ widget_ids! {
|
||||
pub struct Map<'a> {
|
||||
_show: &'a Show,
|
||||
client: &'a Client,
|
||||
_world_map: Id,
|
||||
world_map: (Id, Vec2<u32>),
|
||||
imgs: &'a Imgs,
|
||||
fonts: &'a Fonts,
|
||||
#[conrod(common_builder)]
|
||||
@ -43,7 +43,7 @@ impl<'a> Map<'a> {
|
||||
show: &'a Show,
|
||||
client: &'a Client,
|
||||
imgs: &'a Imgs,
|
||||
world_map: Id,
|
||||
world_map: (Id, Vec2<u32>),
|
||||
fonts: &'a Fonts,
|
||||
pulse: f32,
|
||||
velocity: f32,
|
||||
@ -51,7 +51,7 @@ impl<'a> Map<'a> {
|
||||
Self {
|
||||
_show: show,
|
||||
imgs,
|
||||
_world_map: world_map,
|
||||
world_map,
|
||||
client,
|
||||
fonts: fonts,
|
||||
common: widget::CommonBuilder::default(),
|
||||
@ -159,7 +159,10 @@ impl<'a> Widget for Map<'a> {
|
||||
}
|
||||
|
||||
// Map Image
|
||||
Image::new(/*self.world_map*/ self.imgs.map_placeholder)
|
||||
let (world_map, worldsize) = self.world_map;
|
||||
let worldsize = worldsize.map2(TerrainChunkSize::RECT_SIZE, |e, f| e as f64 * f as f64);
|
||||
|
||||
Image::new(world_map)
|
||||
.middle_of(state.ids.map_bg)
|
||||
.color(Some(Color::Rgba(1.0, 1.0, 1.0, fade - 0.1)))
|
||||
.w_h(700.0, 700.0)
|
||||
@ -174,9 +177,8 @@ impl<'a> Widget for Map<'a> {
|
||||
.get(self.client.entity())
|
||||
.map_or(Vec3::zero(), |pos| pos.0);
|
||||
|
||||
let worldsize = 32768.0; // TODO This has to get the actual world size and not be hardcoded
|
||||
let x = player_pos.x as f64 / worldsize * 700.0/*= x-Size of the map image*/;
|
||||
let y = (/*1.0 -*/player_pos.y as f64 / worldsize) * 700.0;
|
||||
let x = player_pos.x as f64 / worldsize.x * 700.0;
|
||||
let y = player_pos.y as f64 / worldsize.y * 700.0;
|
||||
let indic_ani = (self.pulse * 6.0/*animation speed*/).cos()/*starts at 1.0*/ * 0.5 + 0.50; // changes the animation frame
|
||||
let indic_scale = 1.2;
|
||||
// Indicator
|
||||
|
@ -1,6 +1,6 @@
|
||||
use super::{img_ids::Imgs, Fonts, Show, HP_COLOR, TEXT_COLOR};
|
||||
use client::{self, Client};
|
||||
use common::comp;
|
||||
use common::{comp, terrain::TerrainChunkSize, vol::RectVolSize};
|
||||
use conrod_core::{
|
||||
color,
|
||||
image::Id,
|
||||
@ -33,7 +33,7 @@ pub struct MiniMap<'a> {
|
||||
client: &'a Client,
|
||||
|
||||
imgs: &'a Imgs,
|
||||
_world_map: Id,
|
||||
world_map: (Id, Vec2<u32>),
|
||||
fonts: &'a Fonts,
|
||||
#[conrod(common_builder)]
|
||||
common: widget::CommonBuilder,
|
||||
@ -46,7 +46,7 @@ impl<'a> MiniMap<'a> {
|
||||
show: &'a Show,
|
||||
client: &'a Client,
|
||||
imgs: &'a Imgs,
|
||||
world_map: Id,
|
||||
world_map: (Id, Vec2<u32>),
|
||||
fonts: &'a Fonts,
|
||||
pulse: f32,
|
||||
zoom: f32,
|
||||
@ -55,7 +55,7 @@ impl<'a> MiniMap<'a> {
|
||||
show,
|
||||
client,
|
||||
imgs,
|
||||
_world_map: world_map,
|
||||
world_map,
|
||||
fonts: fonts,
|
||||
common: widget::CommonBuilder::default(),
|
||||
pulse,
|
||||
@ -135,7 +135,9 @@ impl<'a> Widget for MiniMap<'a> {
|
||||
}
|
||||
}*/
|
||||
// Map Image
|
||||
Image::new(/*self.world_map*/ self.imgs.map_placeholder)
|
||||
let (world_map, worldsize) = self.world_map;
|
||||
let worldsize = worldsize.map2(TerrainChunkSize::RECT_SIZE, |e, f| e as f64 * f as f64);
|
||||
Image::new(world_map)
|
||||
.middle_of(state.ids.mmap_frame_bg)
|
||||
.w_h(92.0 * 4.0 * zoom, 82.0 * 4.0 * zoom)
|
||||
.parent(state.ids.mmap_frame_bg)
|
||||
@ -149,9 +151,8 @@ impl<'a> Widget for MiniMap<'a> {
|
||||
.get(self.client.entity())
|
||||
.map_or(Vec3::zero(), |pos| pos.0);
|
||||
|
||||
let worldsize = 32768.0; // TODO This has to get the actual world size and not be hardcoded
|
||||
let x = player_pos.x as f64 / worldsize * 92.0 * 4.0;
|
||||
let y = (/*1.0X-*/player_pos.y as f64 / worldsize) * 82.0 * 4.0;
|
||||
let x = player_pos.x as f64 / worldsize.x * 92.0 * 4.0;
|
||||
let y = player_pos.y as f64 / worldsize.y * 82.0 * 4.0;
|
||||
let indic_ani = (self.pulse * 6.0).cos() * 0.5 + 0.5; //Animation timer
|
||||
let indic_scale = 0.8;
|
||||
// Indicator
|
||||
|
@ -425,7 +425,7 @@ impl Show {
|
||||
pub struct Hud {
|
||||
ui: Ui,
|
||||
ids: Ids,
|
||||
world_map: Id,
|
||||
world_map: (Id, Vec2<u32>),
|
||||
imgs: Imgs,
|
||||
item_imgs: ItemImgs,
|
||||
fonts: Fonts,
|
||||
@ -454,7 +454,10 @@ impl Hud {
|
||||
// Generate ids.
|
||||
let ids = Ids::new(ui.id_generator());
|
||||
// Load world map
|
||||
let world_map = ui.add_graphic(Graphic::Image(client.world_map.clone()));
|
||||
let world_map = (
|
||||
ui.add_graphic(Graphic::Image(client.world_map.0.clone())),
|
||||
client.world_map.1,
|
||||
);
|
||||
// Load images.
|
||||
let imgs = Imgs::load(&mut ui).expect("Failed to load images!");
|
||||
// Load rotation images.
|
||||
|
@ -51,7 +51,7 @@ impl ClientInit {
|
||||
|
||||
let mut last_err = None;
|
||||
|
||||
'tries: for _ in 0..60 + 1 {
|
||||
'tries: for _ in 0..960 + 1 {
|
||||
// 300 Seconds
|
||||
if cancel2.load(Ordering::Relaxed) {
|
||||
break;
|
||||
|
@ -175,7 +175,9 @@ fn mesh_worker<V: BaseVol<Vox = Block> + RectRasterableVol + ReadVol + Debug>(
|
||||
let kind = volume.get(wpos).unwrap_or(&Block::empty()).kind();
|
||||
|
||||
if let Some(cfg) = sprite_config_for(kind) {
|
||||
let seed = wpos.x * 3 + wpos.y * 7 + wpos.x * wpos.y; // Awful PRNG
|
||||
let seed = wpos.x as u64 * 3
|
||||
+ wpos.y as u64 * 7
|
||||
+ wpos.x as u64 * wpos.y as u64; // Awful PRNG
|
||||
|
||||
let instance = SpriteInstance::new(
|
||||
Mat4::identity()
|
||||
|
@ -5,22 +5,28 @@ authors = ["Joshua Barretto <joshua.s.barretto@gmail.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
bincode = "1.2.0"
|
||||
common = { package = "veloren-common", path = "../common" }
|
||||
bitvec = "0.15.2"
|
||||
image = "0.22.3"
|
||||
itertools = "0.8.2"
|
||||
vek = "0.9.9"
|
||||
noise = { version = "0.6.0", default-features = false }
|
||||
num = "0.2.0"
|
||||
ordered-float = "1.0"
|
||||
hashbrown = { version = "0.6.2", features = ["serde"] }
|
||||
hashbrown = { version = "0.6.2", features = ["rayon", "serde", "nightly"] }
|
||||
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.2.0"
|
||||
packed_simd = "0.3.3"
|
||||
rayon = "^1.3.0"
|
||||
roots = "0.0.5"
|
||||
serde = "1.0.102"
|
||||
serde_derive = "1.0.102"
|
||||
ron = "0.5.1"
|
||||
|
||||
[dev-dependencies]
|
||||
minifb = { git = "https://github.com/emoon/rust_minifb.git" }
|
||||
pretty_env_logger = "0.3.0"
|
||||
|
@ -1,12 +1,18 @@
|
||||
use std::ops::{Add, Mul, Sub};
|
||||
use vek::*;
|
||||
use veloren_world::{util::Sampler, World};
|
||||
use veloren_world::{sim::WorldOpts, util::Sampler, World};
|
||||
|
||||
const W: usize = 640;
|
||||
const H: usize = 480;
|
||||
|
||||
fn main() {
|
||||
let world = World::generate(0);
|
||||
let world = World::generate(
|
||||
0,
|
||||
WorldOpts {
|
||||
seed_elements: true,
|
||||
..WorldOpts::default()
|
||||
},
|
||||
);
|
||||
|
||||
let sampler = world.sample_columns();
|
||||
|
||||
|
@ -1,6 +1,8 @@
|
||||
use common::{terrain::TerrainChunkSize, vol::RectVolSize};
|
||||
use std::{f64, io::Write, path::PathBuf, time::SystemTime};
|
||||
use vek::*;
|
||||
use veloren_world::{
|
||||
sim::{RiverKind, WORLD_SIZE},
|
||||
sim::{self, MapConfig, MapDebug, WorldOpts, WORLD_SIZE},
|
||||
World, CONFIG,
|
||||
};
|
||||
|
||||
@ -8,77 +10,241 @@ const W: usize = 1024;
|
||||
const H: usize = 1024;
|
||||
|
||||
fn main() {
|
||||
let world = World::generate(1337);
|
||||
pretty_env_logger::init();
|
||||
|
||||
let sampler = world.sim();
|
||||
// To load a map file of your choice, replace map_file with the name of your map (stored
|
||||
// locally in the map directory of your Veloren root), and swap the sim::FileOpts::Save line
|
||||
// below for the sim::FileOpts::Load one.
|
||||
let map_file =
|
||||
// "map_1575990726223.bin";
|
||||
// "map_1575987666972.bin";
|
||||
"map_1576046079066.bin";
|
||||
let mut _map_file = PathBuf::from("./maps");
|
||||
_map_file.push(map_file);
|
||||
|
||||
let world = World::generate(
|
||||
5284,
|
||||
WorldOpts {
|
||||
seed_elements: false,
|
||||
// world_file: sim::FileOpts::Load(_map_file),
|
||||
world_file: sim::FileOpts::Save,
|
||||
..WorldOpts::default()
|
||||
},
|
||||
);
|
||||
|
||||
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;
|
||||
let sampler = world.sim();
|
||||
|
||||
let mut focus = Vec3::new(0.0, 0.0, CONFIG.sea_level as f64);
|
||||
// Altitude is divided by gain and clamped to [0, 1]; thus, decreasing gain makes
|
||||
// smaller differences in altitude appear larger.
|
||||
let mut gain = CONFIG.mountain_scale;
|
||||
// The Z component during normal calculations is multiplied by gain; thus,
|
||||
let mut lgain = 1.0;
|
||||
let mut scale = WORLD_SIZE.x as f64 / W as f64;
|
||||
|
||||
// Right-handed coordinate system: light is going left, down, and "backwards" (i.e. on the
|
||||
// map, where we translate the y coordinate on the world map to z in the coordinate system,
|
||||
// the light comes from -y on the map and points towards +y on the map). In a right
|
||||
// handed coordinate system, the "camera" points towards -z, so positive z is backwards
|
||||
// "into" the camera.
|
||||
//
|
||||
// "In world space the x-axis will be pointing east, the y-axis up and the z-axis will be pointing south"
|
||||
let mut light_direction = Vec3::new(-0.8, -1.0, 0.3);
|
||||
|
||||
let mut is_basement = false;
|
||||
let mut is_water = true;
|
||||
let mut is_shaded = true;
|
||||
let mut is_temperature = true;
|
||||
let mut is_humidity = true;
|
||||
|
||||
while win.is_open() {
|
||||
let config = MapConfig {
|
||||
dimensions: Vec2::new(W, H),
|
||||
focus,
|
||||
gain,
|
||||
lgain,
|
||||
scale,
|
||||
light_direction,
|
||||
|
||||
is_basement,
|
||||
is_water,
|
||||
is_shaded,
|
||||
is_temperature,
|
||||
is_humidity,
|
||||
is_debug: true,
|
||||
};
|
||||
|
||||
let mut buf = vec![0; W * H];
|
||||
let MapDebug {
|
||||
rivers,
|
||||
lakes,
|
||||
oceans,
|
||||
quads,
|
||||
} = config.generate(sampler, |pos, (r, g, b, a)| {
|
||||
let i = pos.x;
|
||||
let j = pos.y;
|
||||
buf[j * W + i] = u32::from_le_bytes([b, g, r, a]);
|
||||
});
|
||||
|
||||
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]),
|
||||
if win.is_key_down(minifb::Key::F4) {
|
||||
if let Some(len) = (W * H)
|
||||
.checked_mul(scale as usize)
|
||||
.and_then(|acc| acc.checked_mul(scale as usize))
|
||||
{
|
||||
let x = (W as f64 * scale) as usize;
|
||||
let y = (H as f64 * scale) as usize;
|
||||
let config = sim::MapConfig {
|
||||
dimensions: Vec2::new(x, y),
|
||||
scale: 1.0,
|
||||
..config
|
||||
};
|
||||
let mut buf = vec![0u8; 4 * len];
|
||||
config.generate(sampler, |pos, (r, g, b, a)| {
|
||||
let i = pos.x;
|
||||
let j = pos.y;
|
||||
(&mut buf[(j * x + i) * 4..]).write(&[r, g, b, a]).unwrap();
|
||||
});
|
||||
// TODO: Justify fits in u32.
|
||||
let world_map = image::RgbaImage::from_raw(x as u32, y as u32, buf)
|
||||
.expect("Image dimensions must be valid");
|
||||
let mut path = PathBuf::from("./screenshots");
|
||||
if !path.exists() {
|
||||
if let Err(err) = std::fs::create_dir(&path) {
|
||||
log::warn!("Couldn't create folder for screenshot: {:?}", err);
|
||||
}
|
||||
}
|
||||
path.push(format!(
|
||||
"worldmap_{}.png",
|
||||
SystemTime::now()
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.map(|d| d.as_millis())
|
||||
.unwrap_or(0)
|
||||
));
|
||||
if let Err(err) = world_map.save(&path) {
|
||||
log::warn!("Couldn't save screenshot: {:?}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let spd = 32;
|
||||
let spd = 32.0;
|
||||
let lspd = 0.1;
|
||||
if win.is_key_down(minifb::Key::P) {
|
||||
println!(
|
||||
"\
|
||||
Gain / Shade gain: {:?} / {:?}\n\
|
||||
Scale / Focus: {:?} / {:?}\n\
|
||||
Light: {:?}
|
||||
Land(adjacent): (X = temp, Y = humidity): {:?}\n\
|
||||
Rivers: {:?}\n\
|
||||
Lakes: {:?}\n\
|
||||
Oceans: {:?}\n\
|
||||
Total water: {:?}\n\
|
||||
Total land(adjacent): {:?}",
|
||||
gain,
|
||||
lgain,
|
||||
scale,
|
||||
focus,
|
||||
light_direction,
|
||||
quads,
|
||||
rivers,
|
||||
lakes,
|
||||
oceans,
|
||||
rivers + lakes + oceans,
|
||||
quads.iter().map(|x| x.iter().sum::<u32>()).sum::<u32>()
|
||||
);
|
||||
}
|
||||
if win.get_mouse_down(minifb::MouseButton::Left) {
|
||||
if let Some((mx, my)) = win.get_mouse_pos(minifb::MouseMode::Clamp) {
|
||||
let pos = (Vec2::<f64>::from(focus) + (Vec2::new(mx as f64, my as f64) * scale))
|
||||
.map(|e| e as i32);
|
||||
println!(
|
||||
"Chunk position: {:?}",
|
||||
pos.map2(TerrainChunkSize::RECT_SIZE, |e, f| e * f as i32)
|
||||
);
|
||||
}
|
||||
}
|
||||
let is_camera = win.is_key_down(minifb::Key::C);
|
||||
if win.is_key_down(minifb::Key::B) {
|
||||
is_basement ^= true;
|
||||
}
|
||||
if win.is_key_down(minifb::Key::H) {
|
||||
is_humidity ^= true;
|
||||
}
|
||||
if win.is_key_down(minifb::Key::T) {
|
||||
is_temperature ^= true;
|
||||
}
|
||||
if win.is_key_down(minifb::Key::O) {
|
||||
is_water ^= true;
|
||||
}
|
||||
if win.is_key_down(minifb::Key::L) {
|
||||
is_shaded ^= true;
|
||||
}
|
||||
if win.is_key_down(minifb::Key::W) {
|
||||
focus.y -= spd * scale;
|
||||
if is_camera {
|
||||
light_direction.z -= lspd;
|
||||
} else {
|
||||
focus.y -= spd * scale;
|
||||
}
|
||||
}
|
||||
if win.is_key_down(minifb::Key::A) {
|
||||
focus.x -= spd * scale;
|
||||
if is_camera {
|
||||
light_direction.x -= lspd;
|
||||
} else {
|
||||
focus.x -= spd * scale;
|
||||
}
|
||||
}
|
||||
if win.is_key_down(minifb::Key::S) {
|
||||
focus.y += spd * scale;
|
||||
if is_camera {
|
||||
light_direction.z += lspd;
|
||||
} else {
|
||||
focus.y += spd * scale;
|
||||
}
|
||||
}
|
||||
if win.is_key_down(minifb::Key::D) {
|
||||
focus.x += spd * scale;
|
||||
if is_camera {
|
||||
light_direction.x += lspd;
|
||||
} else {
|
||||
focus.x += spd * scale;
|
||||
}
|
||||
}
|
||||
if win.is_key_down(minifb::Key::Q) {
|
||||
_gain += 10.0;
|
||||
if is_camera {
|
||||
if (lgain * 2.0).is_normal() {
|
||||
lgain *= 2.0;
|
||||
}
|
||||
} else {
|
||||
gain += 64.0;
|
||||
}
|
||||
}
|
||||
if win.is_key_down(minifb::Key::E) {
|
||||
_gain -= 10.0;
|
||||
if is_camera {
|
||||
if (lgain / 2.0).is_normal() {
|
||||
lgain /= 2.0;
|
||||
}
|
||||
} else {
|
||||
gain = (gain - 64.0).max(64.0);
|
||||
}
|
||||
}
|
||||
if win.is_key_down(minifb::Key::R) {
|
||||
scale += 1;
|
||||
if is_camera {
|
||||
focus.z += spd * scale;
|
||||
} else {
|
||||
if (scale * 2.0).is_normal() {
|
||||
scale *= 2.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
if win.is_key_down(minifb::Key::F) {
|
||||
scale = (scale - 1).max(0);
|
||||
if is_camera {
|
||||
focus.z -= spd * scale;
|
||||
} else {
|
||||
if (scale / 2.0).is_normal() {
|
||||
scale /= 2.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
win.update_with_buffer(&buf).unwrap();
|
||||
|
@ -4,39 +4,36 @@ use crate::{
|
||||
column::{ColumnGen, ColumnSample},
|
||||
generator::{Generator, TownGen},
|
||||
util::{RandomField, Sampler, SmallCache},
|
||||
World, CONFIG,
|
||||
CONFIG,
|
||||
};
|
||||
use common::{
|
||||
terrain::{structure::StructureBlock, Block, BlockKind, Structure},
|
||||
util::saturate_srgb,
|
||||
vol::{ReadVol, Vox},
|
||||
};
|
||||
use std::ops::{Add, Div, Mul, Neg};
|
||||
use vek::*;
|
||||
|
||||
pub struct BlockGen<'a> {
|
||||
world: &'a World,
|
||||
column_cache: SmallCache<Option<ColumnSample<'a>>>,
|
||||
column_gen: ColumnGen<'a>,
|
||||
pub column_cache: SmallCache<Option<ColumnSample<'a>>>,
|
||||
pub column_gen: ColumnGen<'a>,
|
||||
}
|
||||
|
||||
impl<'a> BlockGen<'a> {
|
||||
pub fn new(world: &'a World, column_gen: ColumnGen<'a>) -> Self {
|
||||
pub fn new(column_gen: ColumnGen<'a>) -> Self {
|
||||
Self {
|
||||
world,
|
||||
column_cache: SmallCache::default(),
|
||||
column_gen,
|
||||
}
|
||||
}
|
||||
|
||||
fn sample_column(
|
||||
pub fn sample_column<'b>(
|
||||
column_gen: &ColumnGen<'a>,
|
||||
cache: &mut SmallCache<Option<ColumnSample<'a>>>,
|
||||
cache: &'b mut SmallCache<Option<ColumnSample<'a>>>,
|
||||
wpos: Vec2<i32>,
|
||||
) -> Option<ColumnSample<'a>> {
|
||||
) -> Option<&'b ColumnSample<'a>> {
|
||||
cache
|
||||
.get(Vec2::from(wpos), |wpos| column_gen.get(wpos))
|
||||
.clone()
|
||||
.as_ref()
|
||||
}
|
||||
|
||||
fn get_cliff_height(
|
||||
@ -61,9 +58,17 @@ impl<'a> BlockGen<'a> {
|
||||
{
|
||||
let cliff_pos3d = Vec3::from(*cliff_pos);
|
||||
|
||||
// Conservative range of height: [15.70, 49.33]
|
||||
let height = (RandomField::new(seed + 1).get(cliff_pos3d) % 64) as f32
|
||||
// [0, 63] / (1 + 3 * [0.12, 1.32]) + 3 =
|
||||
// [0, 63] / (1 + [0.36, 3.96]) + 3 =
|
||||
// [0, 63] / [1.36, 4.96] + 3 =
|
||||
// [0, 63] / [1.36, 4.96] + 3 =
|
||||
// (height min) [0, 0] + 3 = [3, 3]
|
||||
// (height max) [12.70, 46.33] + 3 = [15.70, 49.33]
|
||||
/ (1.0 + 3.0 * cliff_sample.chaos)
|
||||
+ 3.0;
|
||||
// Conservative range of radius: [8, 47]
|
||||
let radius = RandomField::new(seed + 2).get(cliff_pos3d) % 48 + 8;
|
||||
|
||||
max_height.max(
|
||||
@ -83,7 +88,6 @@ impl<'a> BlockGen<'a> {
|
||||
|
||||
pub fn get_z_cache(&mut self, wpos: Vec2<i32>) -> Option<ZCache<'a>> {
|
||||
let BlockGen {
|
||||
world: _,
|
||||
column_cache,
|
||||
column_gen,
|
||||
} = self;
|
||||
@ -92,40 +96,37 @@ impl<'a> BlockGen<'a> {
|
||||
let sample = column_gen.get(wpos)?;
|
||||
|
||||
// Tree samples
|
||||
let mut structure_samples = [None, None, None, None, None, None, None, None, None];
|
||||
for i in 0..structure_samples.len() {
|
||||
if let Some(st) = sample.close_structures[i] {
|
||||
let st_sample = Self::sample_column(column_gen, column_cache, Vec2::from(st.pos));
|
||||
structure_samples[i] = st_sample;
|
||||
}
|
||||
}
|
||||
|
||||
let mut structures = [None, None, None, None, None, None, None, None, None];
|
||||
for i in 0..structures.len() {
|
||||
if let (Some(st), Some(st_sample)) =
|
||||
(sample.close_structures[i], structure_samples[i].clone())
|
||||
{
|
||||
let st_info = match st.meta {
|
||||
None => natural::structure_gen(
|
||||
column_gen,
|
||||
column_cache,
|
||||
i,
|
||||
st.pos,
|
||||
st.seed,
|
||||
&structure_samples,
|
||||
),
|
||||
Some(meta) => Some(StructureInfo {
|
||||
pos: Vec3::from(st.pos) + Vec3::unit_z() * st_sample.alt as i32,
|
||||
seed: st.seed,
|
||||
meta,
|
||||
}),
|
||||
};
|
||||
|
||||
if let Some(st_info) = st_info {
|
||||
structures[i] = Some((st_info, st_sample));
|
||||
sample
|
||||
.close_structures
|
||||
.iter()
|
||||
.zip(structures.iter_mut())
|
||||
.for_each(|(close_structure, structure)| {
|
||||
if let Some(st) = *close_structure {
|
||||
let st_sample =
|
||||
Self::sample_column(column_gen, column_cache, Vec2::from(st.pos));
|
||||
if let Some(st_sample) = st_sample {
|
||||
let st_sample = st_sample.clone();
|
||||
let st_info = match st.meta {
|
||||
None => natural::structure_gen(
|
||||
column_gen,
|
||||
column_cache,
|
||||
st.pos,
|
||||
st.seed,
|
||||
&st_sample,
|
||||
),
|
||||
Some(meta) => Some(StructureInfo {
|
||||
pos: Vec3::from(st.pos) + Vec3::unit_z() * st_sample.alt as i32,
|
||||
seed: st.seed,
|
||||
meta,
|
||||
}),
|
||||
};
|
||||
if let Some(st_info) = st_info {
|
||||
*structure = Some((st_info, st_sample));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Some(ZCache {
|
||||
wpos,
|
||||
@ -141,14 +142,15 @@ impl<'a> BlockGen<'a> {
|
||||
only_structures: bool,
|
||||
) -> Option<Block> {
|
||||
let BlockGen {
|
||||
world,
|
||||
column_cache,
|
||||
column_gen,
|
||||
} = self;
|
||||
let world = column_gen.sim;
|
||||
|
||||
let sample = &z_cache?.sample;
|
||||
let &ColumnSample {
|
||||
alt,
|
||||
basement,
|
||||
chaos,
|
||||
water_level,
|
||||
warp_factor,
|
||||
@ -168,6 +170,7 @@ impl<'a> BlockGen<'a> {
|
||||
temp,
|
||||
humidity,
|
||||
chunk,
|
||||
stone_col,
|
||||
..
|
||||
} = sample;
|
||||
|
||||
@ -176,19 +179,18 @@ impl<'a> BlockGen<'a> {
|
||||
let wposf = wpos.map(|e| e as f64);
|
||||
|
||||
let (block, height) = if !only_structures {
|
||||
let (_definitely_underground, height, on_cliff, water_height) =
|
||||
let (_definitely_underground, height, on_cliff, basement_height, water_height) =
|
||||
if (wposf.z as f32) < alt - 64.0 * chaos {
|
||||
// Shortcut warping
|
||||
(true, alt, false, water_level)
|
||||
(true, alt, false, basement, water_level)
|
||||
} else {
|
||||
// Apply warping
|
||||
let warp = world
|
||||
.sim()
|
||||
.gen_ctx
|
||||
.warp_nz
|
||||
.get(wposf.div(24.0))
|
||||
.mul((chaos - 0.1).max(0.0).powf(2.0))
|
||||
.mul(48.0);
|
||||
.mul((chaos - 0.1).max(0.0).min(1.0).powf(2.0))
|
||||
.mul(16.0);
|
||||
let warp = Lerp::lerp(0.0, warp, warp_factor);
|
||||
|
||||
let surface_height = alt + warp;
|
||||
@ -198,8 +200,8 @@ impl<'a> BlockGen<'a> {
|
||||
(surface_height, false)
|
||||
} else {
|
||||
let turb = Vec2::new(
|
||||
world.sim().gen_ctx.fast_turb_x_nz.get(wposf.div(25.0)) as f32,
|
||||
world.sim().gen_ctx.fast_turb_y_nz.get(wposf.div(25.0)) as f32,
|
||||
world.gen_ctx.fast_turb_x_nz.get(wposf.div(25.0)) as f32,
|
||||
world.gen_ctx.fast_turb_y_nz.get(wposf.div(25.0)) as f32,
|
||||
) * 8.0;
|
||||
|
||||
let wpos_turb = Vec2::from(wpos).map(|e: i32| e as f32) + turb;
|
||||
@ -222,6 +224,7 @@ impl<'a> BlockGen<'a> {
|
||||
false,
|
||||
height,
|
||||
on_cliff,
|
||||
basement + height - alt,
|
||||
(if water_level <= alt {
|
||||
water_level + warp
|
||||
} else {
|
||||
@ -232,8 +235,7 @@ impl<'a> BlockGen<'a> {
|
||||
|
||||
// Sample blocks
|
||||
|
||||
// let stone_col = Rgb::new(240, 230, 220);
|
||||
let stone_col = Rgb::new(195, 187, 201);
|
||||
// let stone_col = Rgb::new(195, 187, 201);
|
||||
|
||||
// let dirt_col = Rgb::new(79, 67, 60);
|
||||
|
||||
@ -246,13 +248,14 @@ impl<'a> BlockGen<'a> {
|
||||
|
||||
let water = Block::new(BlockKind::Water, Rgb::new(60, 90, 190));
|
||||
|
||||
let grass_depth = 1.5 + 2.0 * chaos;
|
||||
let grass_depth = (1.5 + 2.0 * chaos).min(height - basement_height);
|
||||
let block = if (wposf.z as f32) < height - grass_depth {
|
||||
let col = Lerp::lerp(
|
||||
saturate_srgb(sub_surface_color, 0.45).map(|e| (e * 255.0) as u8),
|
||||
stone_col,
|
||||
sub_surface_color,
|
||||
stone_col.map(|e| e as f32 / 255.0),
|
||||
(height - grass_depth - wposf.z as f32) * 0.15,
|
||||
);
|
||||
)
|
||||
.map(|e| (e * 255.0) as u8);
|
||||
|
||||
// Underground
|
||||
if (wposf.z as f32) > alt - 32.0 * chaos {
|
||||
@ -271,7 +274,7 @@ impl<'a> BlockGen<'a> {
|
||||
// Surface
|
||||
Some(Block::new(
|
||||
BlockKind::Normal,
|
||||
saturate_srgb(col, 0.45).map(|e| (e * 255.0) as u8),
|
||||
col.map(|e| (e * 255.0) as u8),
|
||||
))
|
||||
} else if (wposf.z as f32) < height + 0.9
|
||||
&& temp < CONFIG.desert_temp
|
||||
@ -337,9 +340,9 @@ impl<'a> BlockGen<'a> {
|
||||
.or_else(|| {
|
||||
// Rocks
|
||||
if (height + 2.5 - wposf.z as f32).div(7.5).abs().powf(2.0) < rock {
|
||||
let field0 = RandomField::new(world.sim().seed + 0);
|
||||
let field1 = RandomField::new(world.sim().seed + 1);
|
||||
let field2 = RandomField::new(world.sim().seed + 2);
|
||||
let field0 = RandomField::new(world.seed + 0);
|
||||
let field1 = RandomField::new(world.seed + 1);
|
||||
let field2 = RandomField::new(world.seed + 2);
|
||||
|
||||
Some(Block::new(
|
||||
BlockKind::Normal,
|
||||
@ -421,7 +424,7 @@ impl<'a> ZCache<'a> {
|
||||
0.0
|
||||
};
|
||||
|
||||
let min = self.sample.alt - (self.sample.chaos * 48.0 + cave_depth);
|
||||
let min = self.sample.alt - (self.sample.chaos.min(1.0) * 16.0 + cave_depth);
|
||||
let min = min - 4.0;
|
||||
|
||||
let cliff = BlockGen::get_cliff_height(
|
||||
|
@ -23,13 +23,10 @@ static QUIRKY_RAND: RandomPerm = RandomPerm::new(0xA634460F);
|
||||
pub fn structure_gen<'a>(
|
||||
column_gen: &ColumnGen<'a>,
|
||||
column_cache: &mut SmallCache<Option<ColumnSample<'a>>>,
|
||||
idx: usize,
|
||||
st_pos: Vec2<i32>,
|
||||
st_seed: u32,
|
||||
structure_samples: &[Option<ColumnSample>; 9],
|
||||
st_sample: &ColumnSample,
|
||||
) -> Option<StructureInfo> {
|
||||
let st_sample = &structure_samples[idx].as_ref()?;
|
||||
|
||||
// Assuming it's a tree... figure out when it SHOULDN'T spawn
|
||||
let random_seed = (st_seed as f64) / (u32::MAX as f64);
|
||||
if (st_sample.tree_density as f64) < random_seed
|
||||
@ -64,6 +61,7 @@ pub fn structure_gen<'a>(
|
||||
ForestKind::Savannah => &ACACIAS,
|
||||
ForestKind::Oak if QUIRKY_RAND.get(st_seed) % 16 == 7 => &OAK_STUMPS,
|
||||
ForestKind::Oak if QUIRKY_RAND.get(st_seed) % 8 == 7 => &FRUIT_TREES,
|
||||
ForestKind::Oak if QUIRKY_RAND.get(st_seed) % 14 == 7 => &BIRCHES,
|
||||
ForestKind::Oak => &OAKS,
|
||||
ForestKind::Pine => &PINES,
|
||||
ForestKind::SnowPine => &SNOW_PINES,
|
||||
@ -119,6 +117,7 @@ lazy_static! {
|
||||
pub static ref SNOW_PINES: Vec<Arc<Structure>> = load_structures("snow_pines");
|
||||
pub static ref ACACIAS: Vec<Arc<Structure>> = load_structures("acacias");
|
||||
pub static ref FRUIT_TREES: Vec<Arc<Structure>> = load_structures("fruit_trees");
|
||||
pub static ref BIRCHES: Vec<Arc<Structure>> = load_structures("birch");
|
||||
pub static ref MANGROVE_TREES: Vec<Arc<Structure>> = load_structures("mangrove_trees");
|
||||
pub static ref QUIRKY: Vec<Arc<Structure>> = load_structures("quirky");
|
||||
pub static ref QUIRKY_DRY: Vec<Arc<Structure>> = load_structures("quirky_dry");
|
||||
|
@ -233,7 +233,6 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
|
||||
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);
|
||||
@ -438,13 +437,13 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
|
||||
// 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 {
|
||||
neighbor_river_data.for_each(|(river_chunk_idx, river_chunk, river, dist)| {
|
||||
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;
|
||||
return;
|
||||
} else {
|
||||
let river_dist = dist.map(|(_, dist, _, (river_t, _, downhill_river))| {
|
||||
let downhill_height = if kind.is_river() {
|
||||
@ -499,7 +498,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
|
||||
let river_dist = dist.y;
|
||||
|
||||
if !(dist.x == 0.0 && river_dist < scale_factor) {
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
// We basically want to project outwards from river_pos, along the current
|
||||
// tangent line, to chunks <= river_width * 1.0 away from this
|
||||
@ -526,7 +525,8 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let river_scale_factor = if river_count == 0.0 {
|
||||
1.0
|
||||
} else {
|
||||
@ -553,23 +553,23 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
|
||||
.gen_ctx
|
||||
.small_nz
|
||||
.get((wposf_turb.div(128.0)).into_array()) as f32)
|
||||
.mul(24.0);
|
||||
.mul(4.0);
|
||||
|
||||
let riverless_alt_delta = (sim
|
||||
.gen_ctx
|
||||
.small_nz
|
||||
.get((wposf_turb.div(200.0)).into_array()) as f32)
|
||||
let riverless_alt_delta = (sim.gen_ctx.small_nz.get(
|
||||
(wposf_turb.div(200.0 * (32.0 / TerrainChunkSize::RECT_SIZE.x as f64))).into_array(),
|
||||
) as f32)
|
||||
.min(1.0)
|
||||
.max(-1.0)
|
||||
.abs()
|
||||
.mul(chaos.max(0.05))
|
||||
.mul(27.0)
|
||||
+ (sim
|
||||
.gen_ctx
|
||||
.small_nz
|
||||
.get((wposf_turb.div(400.0)).into_array()) as f32)
|
||||
.mul(3.0)
|
||||
+ (sim.gen_ctx.small_nz.get(
|
||||
(wposf_turb.div(400.0 * (32.0 / TerrainChunkSize::RECT_SIZE.x as f64)))
|
||||
.into_array(),
|
||||
) as f32)
|
||||
.min(1.0)
|
||||
.max(-1.0)
|
||||
.abs()
|
||||
.mul((1.0 - chaos).max(0.3))
|
||||
.mul(1.0 - humidity)
|
||||
.mul(32.0);
|
||||
.mul(3.0);
|
||||
|
||||
let downhill = sim_chunk.downhill;
|
||||
let downhill_pos = downhill.and_then(|downhill_pos| sim.get(downhill_pos));
|
||||
@ -750,7 +750,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
|
||||
lake_dist <= TerrainChunkSize::RECT_SIZE.x as f64 * 0.5;
|
||||
if gouge_factor == 1.0 {
|
||||
return Some((
|
||||
alt_for_river < lake_water_alt || in_bounds,
|
||||
true,
|
||||
alt.min(lake_water_alt - 1.0 - river_gouge),
|
||||
downhill_water_alt.max(lake_water_alt)
|
||||
- river_gouge,
|
||||
@ -758,7 +758,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
|
||||
));
|
||||
} else {
|
||||
return Some((
|
||||
alt_for_river < lake_water_alt || in_bounds,
|
||||
true,
|
||||
alt_for_river,
|
||||
if in_bounds_ {
|
||||
downhill_water_alt.max(lake_water_alt)
|
||||
@ -799,9 +799,13 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
|
||||
} else {
|
||||
(false, alt_for_river, downhill_water_alt, 1.0)
|
||||
};
|
||||
// NOTE: To disable warp, uncomment this line.
|
||||
// let warp_factor = 0.0;
|
||||
|
||||
let riverless_alt_delta = Lerp::lerp(0.0, riverless_alt_delta, warp_factor);
|
||||
let alt = alt_ + riverless_alt_delta;
|
||||
let basement =
|
||||
alt + sim.get_interpolated_monotone(wpos, |chunk| chunk.basement.sub(chunk.alt))?;
|
||||
|
||||
let rock = (sim.gen_ctx.small_nz.get(
|
||||
Vec3::new(wposf.x, wposf.y, alt as f64)
|
||||
@ -825,20 +829,19 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
|
||||
.mul(0.5)
|
||||
.add(marble_small.sub(0.5).mul(0.25));
|
||||
|
||||
let temp = temp.add((marble - 0.5) * 0.25);
|
||||
let humidity = humidity.add((marble - 0.5) * 0.25);
|
||||
|
||||
// Colours
|
||||
let cold_grass = Rgb::new(0.0, 0.5, 0.25);
|
||||
let warm_grass = Rgb::new(0.4, 0.8, 0.0);
|
||||
let dark_grass = Rgb::new(0.15, 0.4, 0.1);
|
||||
let wet_grass = Rgb::new(0.1, 0.8, 0.2);
|
||||
let cold_stone = Rgb::new(0.57, 0.67, 0.8);
|
||||
let hot_stone = Rgb::new(0.07, 0.07, 0.06);
|
||||
let warm_stone = Rgb::new(0.77, 0.77, 0.64);
|
||||
let beach_sand = Rgb::new(0.9, 0.82, 0.6);
|
||||
let desert_sand = Rgb::new(0.95, 0.75, 0.5);
|
||||
let snow = Rgb::new(0.8, 0.85, 1.0);
|
||||
|
||||
let stone_col = Rgb::new(195, 187, 201);
|
||||
let dirt = Lerp::lerp(
|
||||
Rgb::new(0.075, 0.07, 0.3),
|
||||
Rgb::new(0.75, 0.55, 0.1),
|
||||
@ -846,7 +849,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
|
||||
);
|
||||
let tundra = Lerp::lerp(snow, Rgb::new(0.01, 0.3, 0.0), 0.4 + marble * 0.6);
|
||||
let dead_tundra = Lerp::lerp(warm_stone, Rgb::new(0.3, 0.12, 0.2), marble);
|
||||
let cliff = Rgb::lerp(cold_stone, warm_stone, marble);
|
||||
let cliff = Rgb::lerp(cold_stone, hot_stone, marble);
|
||||
|
||||
let grass = Rgb::lerp(
|
||||
cold_grass,
|
||||
@ -874,18 +877,23 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
|
||||
|
||||
// For below desert humidity, we are always sand or rock, depending on altitude and
|
||||
// temperature.
|
||||
let ground = Rgb::lerp(
|
||||
Rgb::lerp(
|
||||
let ground = Lerp::lerp(
|
||||
Lerp::lerp(
|
||||
dead_tundra,
|
||||
sand,
|
||||
temp.sub(CONFIG.snow_temp)
|
||||
.div(CONFIG.desert_temp.sub(CONFIG.snow_temp))
|
||||
.mul(0.5),
|
||||
),
|
||||
cliff,
|
||||
alt.sub(CONFIG.mountain_scale * 0.25)
|
||||
.div(CONFIG.mountain_scale * 0.125),
|
||||
dirt,
|
||||
humidity
|
||||
.sub(CONFIG.desert_hum)
|
||||
.div(CONFIG.forest_hum.sub(CONFIG.desert_hum))
|
||||
.mul(1.0),
|
||||
);
|
||||
|
||||
let sub_surface_color = Lerp::lerp(cliff, ground, alt.sub(basement).mul(0.25));
|
||||
|
||||
// From desert to forest humidity, we go from tundra to dirt to grass to moss to sand,
|
||||
// depending on temperature.
|
||||
let ground = Rgb::lerp(
|
||||
@ -895,17 +903,19 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
|
||||
Rgb::lerp(
|
||||
Rgb::lerp(
|
||||
tundra,
|
||||
// snow_temp to 0
|
||||
// snow_temp to temperate_temp
|
||||
dirt,
|
||||
temp.sub(CONFIG.snow_temp)
|
||||
.div(CONFIG.snow_temp.neg())
|
||||
.div(CONFIG.temperate_temp.sub(CONFIG.snow_temp))
|
||||
/*.sub((marble - 0.5) * 0.05)
|
||||
.mul(256.0)*/
|
||||
.mul(1.0),
|
||||
),
|
||||
// 0 to tropical_temp
|
||||
// temperate_temp to tropical_temp
|
||||
grass,
|
||||
temp.div(CONFIG.tropical_temp).mul(4.0),
|
||||
temp.sub(CONFIG.temperate_temp)
|
||||
.div(CONFIG.tropical_temp.sub(CONFIG.temperate_temp))
|
||||
.mul(4.0),
|
||||
),
|
||||
// tropical_temp to desert_temp
|
||||
moss,
|
||||
@ -932,9 +942,11 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
|
||||
Rgb::lerp(
|
||||
Rgb::lerp(
|
||||
snow_moss,
|
||||
// 0 to tropical_temp
|
||||
// temperate_temp to tropical_temp
|
||||
grass,
|
||||
temp.div(CONFIG.tropical_temp).mul(4.0),
|
||||
temp.sub(CONFIG.temperate_temp)
|
||||
.div(CONFIG.tropical_temp.sub(CONFIG.temperate_temp))
|
||||
.mul(4.0),
|
||||
),
|
||||
// tropical_temp to desert_temp
|
||||
tropical,
|
||||
@ -960,9 +972,11 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
|
||||
Rgb::lerp(
|
||||
Rgb::lerp(
|
||||
snow_moss,
|
||||
// 0 to tropical_temp
|
||||
// temperate_temp to tropical_temp
|
||||
rainforest,
|
||||
temp.div(CONFIG.tropical_temp).mul(4.0),
|
||||
temp.sub(CONFIG.temperate_temp)
|
||||
.div(CONFIG.tropical_temp.sub(CONFIG.temperate_temp))
|
||||
.mul(4.0),
|
||||
),
|
||||
// tropical_temp to desert_temp
|
||||
tropical,
|
||||
@ -980,14 +994,21 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
|
||||
);
|
||||
|
||||
// Snow covering
|
||||
let ground = Rgb::lerp(
|
||||
snow,
|
||||
ground,
|
||||
temp.sub(CONFIG.snow_temp)
|
||||
.max(-humidity.sub(CONFIG.desert_hum))
|
||||
.mul(16.0)
|
||||
.add((marble_small - 0.5) * 0.5),
|
||||
);
|
||||
let snow_cover = temp
|
||||
.sub(CONFIG.snow_temp)
|
||||
.max(-humidity.sub(CONFIG.desert_hum))
|
||||
.mul(16.0)
|
||||
.add((marble_small - 0.5) * 0.5);
|
||||
let (alt, ground, sub_surface_color) = if snow_cover <= 0.5 && alt > water_level {
|
||||
// Allow snow cover.
|
||||
(
|
||||
alt + 1.0 - snow_cover.max(0.0),
|
||||
Rgb::lerp(snow, ground, snow_cover),
|
||||
Lerp::lerp(sub_surface_color, ground, alt.sub(basement).mul(0.15)),
|
||||
)
|
||||
} else {
|
||||
(alt, ground, sub_surface_color)
|
||||
};
|
||||
|
||||
// Caves
|
||||
let cave_at = |wposf: Vec2<f64>| {
|
||||
@ -999,7 +1020,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
|
||||
.powf(2.0)
|
||||
.neg()
|
||||
.add(1.0)
|
||||
.mul((1.15 - chaos).min(1.0))
|
||||
.mul((1.32 - chaos).min(1.0))
|
||||
};
|
||||
let cave_xy = cave_at(wposf);
|
||||
let cave_alt = alt - 24.0
|
||||
@ -1035,18 +1056,20 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
|
||||
|
||||
Some(ColumnSample {
|
||||
alt,
|
||||
basement,
|
||||
chaos,
|
||||
water_level,
|
||||
warp_factor,
|
||||
surface_color: Rgb::lerp(
|
||||
sand,
|
||||
Rgb::lerp(cliff, sand, alt.sub(basement).mul(0.25)),
|
||||
// Land
|
||||
ground,
|
||||
// Beach
|
||||
((ocean_level - 1.0) / 2.0).max(0.0),
|
||||
),
|
||||
sub_surface_color: dirt,
|
||||
tree_density,
|
||||
sub_surface_color,
|
||||
// No growing directly on bedrock.
|
||||
tree_density: Lerp::lerp(0.0, tree_density, alt.sub(2.0).sub(basement).mul(0.5)),
|
||||
forest_kind: sim_chunk.forest_kind,
|
||||
close_structures: self.gen_close_structures(wpos),
|
||||
cave_xy,
|
||||
@ -1062,6 +1085,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
|
||||
humidity,
|
||||
spawn_rate,
|
||||
location: sim_chunk.location.as_ref(),
|
||||
stone_col,
|
||||
|
||||
chunk: sim_chunk,
|
||||
spawn_rules: sim_chunk
|
||||
@ -1081,6 +1105,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
|
||||
#[derive(Clone)]
|
||||
pub struct ColumnSample<'a> {
|
||||
pub alt: f32,
|
||||
pub basement: f32,
|
||||
pub chaos: f32,
|
||||
pub water_level: f32,
|
||||
pub warp_factor: f32,
|
||||
@ -1102,6 +1127,7 @@ pub struct ColumnSample<'a> {
|
||||
pub humidity: f32,
|
||||
pub spawn_rate: f32,
|
||||
pub location: Option<&'a LocationInfo>,
|
||||
pub stone_col: Rgb<u8>,
|
||||
|
||||
pub chunk: &'a SimChunk,
|
||||
pub spawn_rules: SpawnRules,
|
||||
|
@ -2,12 +2,13 @@ pub struct Config {
|
||||
pub sea_level: f32,
|
||||
pub mountain_scale: f32,
|
||||
pub snow_temp: f32,
|
||||
pub temperate_temp: f32,
|
||||
pub tropical_temp: f32,
|
||||
pub desert_temp: f32,
|
||||
pub desert_hum: f32,
|
||||
pub forest_hum: f32,
|
||||
pub jungle_hum: f32,
|
||||
/// Rainfall (in meters) per chunk per minute. Default is set to make it approximately
|
||||
/// Rainfall (in meters) per m² of surface 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,
|
||||
@ -46,13 +47,14 @@ pub struct Config {
|
||||
pub const CONFIG: Config = Config {
|
||||
sea_level: 140.0,
|
||||
mountain_scale: 2048.0,
|
||||
snow_temp: -0.6,
|
||||
tropical_temp: 0.2,
|
||||
desert_temp: 0.6,
|
||||
snow_temp: -0.8,
|
||||
temperate_temp: -0.4,
|
||||
tropical_temp: 0.4,
|
||||
desert_temp: 0.8,
|
||||
desert_hum: 0.15,
|
||||
forest_hum: 0.5,
|
||||
jungle_hum: 0.85,
|
||||
rainfall_chunk_rate: 1.0 / 512.0,
|
||||
rainfall_chunk_rate: 1.0 / (512.0 * 32.0 * 32.0),
|
||||
river_roughness: 0.06125,
|
||||
river_max_width: 2.0,
|
||||
river_min_height: 0.25,
|
||||
|
@ -3,8 +3,8 @@ mod vol;
|
||||
|
||||
use super::{Generator, SpawnRules};
|
||||
use crate::{
|
||||
block::block_from_structure,
|
||||
column::{ColumnGen, ColumnSample},
|
||||
block::{block_from_structure, BlockGen},
|
||||
column::ColumnSample,
|
||||
util::Sampler,
|
||||
CONFIG,
|
||||
};
|
||||
@ -123,23 +123,27 @@ pub struct TownState {
|
||||
}
|
||||
|
||||
impl TownState {
|
||||
pub fn generate(center: Vec2<i32>, gen: &mut ColumnGen, rng: &mut impl Rng) -> Option<Self> {
|
||||
pub fn generate(center: Vec2<i32>, gen: &mut BlockGen, rng: &mut impl Rng) -> Option<Self> {
|
||||
let radius = rng.gen_range(18, 20) * 9;
|
||||
let size = Vec2::broadcast(radius * 2 / 9 - 2);
|
||||
|
||||
if gen.get(center).map(|sample| sample.chaos).unwrap_or(0.0) > 0.35
|
||||
|| gen.get(center).map(|sample| sample.alt).unwrap_or(0.0) < CONFIG.sea_level + 10.0
|
||||
let center_col = BlockGen::sample_column(&gen.column_gen, &mut gen.column_cache, center);
|
||||
|
||||
if center_col.map(|sample| sample.chaos).unwrap_or(0.0) > 0.35
|
||||
|| center_col.map(|sample| sample.alt).unwrap_or(0.0) < CONFIG.sea_level + 10.0
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
let alt = gen.get(center).map(|sample| sample.alt).unwrap_or(0.0) as i32;
|
||||
let alt = center_col.map(|sample| sample.alt).unwrap_or(0.0) as i32;
|
||||
|
||||
let mut vol = TownVol::generate_from(
|
||||
size,
|
||||
|pos| {
|
||||
let wpos = center + (pos - size / 2) * CELL_SIZE + CELL_SIZE / 2;
|
||||
let rel_alt = gen.get(wpos).map(|sample| sample.alt).unwrap_or(0.0) as i32
|
||||
let rel_alt = BlockGen::sample_column(&gen.column_gen, &mut gen.column_cache, wpos)
|
||||
.map(|sample| sample.alt)
|
||||
.unwrap_or(0.0) as i32
|
||||
+ CELL_HEIGHT / 2
|
||||
- alt;
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
#![deny(unsafe_code)]
|
||||
#![allow(incomplete_features)]
|
||||
#![feature(const_generics, label_break_value)]
|
||||
#![feature(arbitrary_enum_discriminant, const_generics, label_break_value)]
|
||||
|
||||
mod all;
|
||||
mod block;
|
||||
@ -36,9 +36,9 @@ pub struct World {
|
||||
}
|
||||
|
||||
impl World {
|
||||
pub fn generate(seed: u32) -> Self {
|
||||
pub fn generate(seed: u32, opts: sim::WorldOpts) -> Self {
|
||||
Self {
|
||||
sim: sim::WorldSim::generate(seed),
|
||||
sim: sim::WorldSim::generate(seed, opts),
|
||||
}
|
||||
}
|
||||
|
||||
@ -57,7 +57,7 @@ impl World {
|
||||
}
|
||||
|
||||
pub fn sample_blocks(&self) -> BlockGen {
|
||||
BlockGen::new(self, ColumnGen::new(&self.sim))
|
||||
BlockGen::new(ColumnGen::new(&self.sim))
|
||||
}
|
||||
|
||||
pub fn generate_chunk(
|
||||
@ -101,8 +101,8 @@ impl World {
|
||||
let chunk_block_pos = Vec3::from(chunk_pos) * TerrainChunkSize::RECT_SIZE.map(|e| e as i32);
|
||||
|
||||
let mut chunk = TerrainChunk::new(base_z, stone, air, meta);
|
||||
for x in 0..TerrainChunkSize::RECT_SIZE.x as i32 {
|
||||
for y in 0..TerrainChunkSize::RECT_SIZE.y as i32 {
|
||||
for y in 0..TerrainChunkSize::RECT_SIZE.y as i32 {
|
||||
for x in 0..TerrainChunkSize::RECT_SIZE.x as i32 {
|
||||
if should_continue() {
|
||||
return Err(());
|
||||
};
|
||||
@ -116,11 +116,11 @@ impl World {
|
||||
|
||||
let (min_z, only_structures_min_z, max_z) = z_cache.get_z_limits(&mut sampler);
|
||||
|
||||
for z in base_z..min_z as i32 {
|
||||
(base_z..min_z as i32).for_each(|z| {
|
||||
let _ = chunk.set(Vec3::new(x, y, z), stone);
|
||||
}
|
||||
});
|
||||
|
||||
for z in min_z as i32..max_z as i32 {
|
||||
(min_z as i32..max_z as i32).for_each(|z| {
|
||||
let lpos = Vec3::new(x, y, z);
|
||||
let wpos = chunk_block_pos + lpos;
|
||||
let only_structures = lpos.z >= only_structures_min_z as i32;
|
||||
@ -130,7 +130,7 @@ impl World {
|
||||
{
|
||||
let _ = chunk.set(lpos, block);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
469
world/src/sim/diffusion.rs
Normal file
469
world/src/sim/diffusion.rs
Normal file
@ -0,0 +1,469 @@
|
||||
use super::Alt;
|
||||
use rayon::prelude::*;
|
||||
|
||||
/// From https://github.com/fastscape-lem/fastscapelib-fortran/blob/master/src/Diffusion.f90
|
||||
///
|
||||
/// See https://fastscape-lem.github.io/fastscapelib-fortran
|
||||
///
|
||||
/// nx = grid size x
|
||||
///
|
||||
/// ny = grid size y
|
||||
///
|
||||
/// xl = x-dimension of the model topography in meters (double precision)
|
||||
///
|
||||
/// yl = y-dimension of the model topography in meters (double precision)
|
||||
///
|
||||
/// dt = length of the time step in years (double precision)
|
||||
///
|
||||
/// ibc = boundary conditions for grid. For now, we assume all four boundaries are fixed height,
|
||||
/// so this parameter is ignored.
|
||||
///
|
||||
/// h = heights of cells at each cell in the grid.
|
||||
///
|
||||
/// b = basement height at each cell in the grid (see https://en.wikipedia.org/wiki/Basement_(geology)).
|
||||
///
|
||||
/// kd = bedrock transport coefficient (or diffusivity) for hillslope processes in meter squared per year
|
||||
/// (double precision) at each cell in the grid.
|
||||
///
|
||||
/// kdsed = sediment transport coefficient (or diffusivity) for hillslope processes in meter squared per year;
|
||||
/// (double precision;) note that when kdsed < 0, its value is not used, i.e., kd for sediment and bedrock have
|
||||
/// the same value, regardless of sediment thickness
|
||||
/* subroutine Diffusion ()
|
||||
|
||||
! subroutine to solve the diffusion equation by ADI
|
||||
|
||||
use FastScapeContext
|
||||
|
||||
implicit none
|
||||
*/
|
||||
pub fn diffusion(
|
||||
nx: usize,
|
||||
ny: usize,
|
||||
xl: f64,
|
||||
yl: f64,
|
||||
dt: f64,
|
||||
_ibc: (),
|
||||
h: &mut [Alt],
|
||||
b: &mut [Alt],
|
||||
kd: impl Fn(usize) -> f64,
|
||||
kdsed: f64,
|
||||
) {
|
||||
let aij = |i: usize, j: usize| j * nx + i;
|
||||
/*
|
||||
double precision, dimension(:), allocatable :: f,diag,sup,inf,res
|
||||
double precision, dimension(:,:), allocatable :: zint,kdint,zintp
|
||||
integer i,j,ij
|
||||
double precision factxp,factxm,factyp,factym,dx,dy
|
||||
*/
|
||||
let mut f: Vec<f64>;
|
||||
let mut diag: Vec<f64>;
|
||||
let mut sup: Vec<f64>;
|
||||
let mut inf: Vec<f64>;
|
||||
let mut res: Vec<f64>;
|
||||
let mut zint: Vec<f64>;
|
||||
let mut kdint: Vec<f64>;
|
||||
let mut zintp: Vec<f64>;
|
||||
let mut ij: usize;
|
||||
let mut factxp: f64;
|
||||
let mut factxm: f64;
|
||||
let mut factyp: f64;
|
||||
let mut factym: f64;
|
||||
let dx: f64;
|
||||
let dy: f64;
|
||||
/*
|
||||
character cbc*4
|
||||
|
||||
!print*,'Diffusion'
|
||||
|
||||
write (cbc,'(i4)') ibc
|
||||
|
||||
dx=xl/(nx-1)
|
||||
dy=yl/(ny-1)
|
||||
*/
|
||||
// 2048*32/2048/2048
|
||||
// 1 / 64 m
|
||||
dx = xl / /*(nx - 1)*/nx as f64;
|
||||
dy = yl / /*(ny - 1)*/ny as f64;
|
||||
/*
|
||||
! creates 2D internal arrays to store topo and kd
|
||||
|
||||
allocate (zint(nx,ny),kdint(nx,ny),zintp(nx,ny))
|
||||
*/
|
||||
zint = vec![Default::default(); nx * ny];
|
||||
kdint = vec![Default::default(); nx * ny];
|
||||
/*
|
||||
do j=1,ny
|
||||
do i=1,nx
|
||||
ij=(j-1)*nx+i
|
||||
zint(i,j)=h(ij)
|
||||
kdint(i,j)=kd(ij)
|
||||
if (kdsed.gt.0.d0 .and. (h(ij)-b(ij)).gt.1.d-6) kdint(i,j)=kdsed
|
||||
enddo
|
||||
enddo
|
||||
|
||||
zintp = zint
|
||||
*/
|
||||
for j in 0..ny {
|
||||
for i in 0..nx {
|
||||
// ij = vec2_as_uniform_idx(i, j);
|
||||
ij = aij(i, j);
|
||||
zint[ij] = h[ij] as f64;
|
||||
kdint[ij] = kd(ij);
|
||||
if kdsed > 0.0 && (h[ij] - b[ij]) > 1.0e-6 {
|
||||
kdint[ij] = kdsed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
zintp = zint.clone();
|
||||
/*
|
||||
! first pass along the x-axis
|
||||
|
||||
allocate (f(nx),diag(nx),sup(nx),inf(nx),res(nx))
|
||||
f=0.d0
|
||||
diag=0.d0
|
||||
sup=0.d0
|
||||
inf=0.d0
|
||||
res=0.d0
|
||||
do j=2,ny-1
|
||||
*/
|
||||
f = vec![0.0; nx];
|
||||
diag = vec![0.0; nx];
|
||||
sup = vec![0.0; nx];
|
||||
inf = vec![0.0; nx];
|
||||
res = vec![0.0; nx];
|
||||
for j in 1..ny - 1 {
|
||||
/*
|
||||
do i=2,nx-1
|
||||
factxp=(kdint(i+1,j)+kdint(i,j))/2.d0*(dt/2.)/dx**2
|
||||
factxm=(kdint(i-1,j)+kdint(i,j))/2.d0*(dt/2.)/dx**2
|
||||
factyp=(kdint(i,j+1)+kdint(i,j))/2.d0*(dt/2.)/dy**2
|
||||
factym=(kdint(i,j-1)+kdint(i,j))/2.d0*(dt/2.)/dy**2
|
||||
diag(i)=1.d0+factxp+factxm
|
||||
sup(i)=-factxp
|
||||
inf(i)=-factxm
|
||||
f(i)=zintp(i,j)+factyp*zintp(i,j+1)-(factyp+factym)*zintp(i,j)+factym*zintp(i,j-1)
|
||||
enddo
|
||||
*/
|
||||
for i in 1..nx - 1 {
|
||||
factxp = (kdint[aij(i + 1, j)] + kdint[aij(i, j)]) / 2.0 * (dt / 2.0) / (dx * dx);
|
||||
factxm = (kdint[aij(i - 1, j)] + kdint[aij(i, j)]) / 2.0 * (dt / 2.0) / (dx * dx);
|
||||
factyp = (kdint[aij(i, j + 1)] + kdint[aij(i, j)]) / 2.0 * (dt / 2.0) / (dy * dy);
|
||||
factym = (kdint[aij(i, j - 1)] + kdint[aij(i, j)]) / 2.0 * (dt / 2.0) / (dy * dy);
|
||||
diag[i] = 1.0 + factxp + factxm;
|
||||
sup[i] = -factxp;
|
||||
inf[i] = -factxm;
|
||||
f[i] = zintp[aij(i, j)] + factyp * zintp[aij(i, j + 1)]
|
||||
- (factyp + factym) * zintp[aij(i, j)]
|
||||
+ factym * zintp[aij(i, j - 1)];
|
||||
}
|
||||
/*
|
||||
! left bc
|
||||
if (cbc(4:4).eq.'1') then
|
||||
diag(1)=1.
|
||||
sup(1)=0.
|
||||
f(1)=zintp(1,j)
|
||||
else
|
||||
factxp=(kdint(2,j)+kdint(1,j))/2.d0*(dt/2.)/dx**2
|
||||
factyp=(kdint(1,j+1)+kdint(1,j))/2.d0*(dt/2.)/dy**2
|
||||
factym=(kdint(1,j-1)+kdint(1,j))/2.d0*(dt/2.)/dy**2
|
||||
diag(1)=1.d0+factxp
|
||||
sup(1)=-factxp
|
||||
f(1)=zintp(1,j)+factyp*zintp(1,j+1)-(factyp+factym)*zintp(1,j)+factym*zintp(1,j-1)
|
||||
endif
|
||||
*/
|
||||
if true {
|
||||
diag[0] = 1.0;
|
||||
sup[0] = 0.0;
|
||||
f[0] = zintp[aij(0, j)];
|
||||
} else {
|
||||
// reflective boundary
|
||||
factxp = (kdint[aij(1, j)] + kdint[aij(0, j)]) / 2.0 * (dt / 2.0) / (dx * dx);
|
||||
factyp = (kdint[aij(0, j + 1)] + kdint[aij(0, j)]) / 2.0 * (dt / 2.0) / (dy * dy);
|
||||
factym = (kdint[aij(0, j - 1)] + kdint[aij(0, j)]) / 2.0 * (dt / 2.0) / (dy * dy);
|
||||
diag[0] = 1.0 + factxp;
|
||||
sup[0] = -factxp;
|
||||
f[0] = zintp[aij(0, j)] + factyp * zintp[aij(0, j + 1)]
|
||||
- (factyp + factym) * zintp[aij(0, j)]
|
||||
+ factym * zintp[aij(0, j - 1)];
|
||||
}
|
||||
/*
|
||||
! right bc
|
||||
if (cbc(2:2).eq.'1') then
|
||||
diag(nx)=1.
|
||||
inf(nx)=0.
|
||||
f(nx)=zintp(nx,j)
|
||||
else
|
||||
factxm=(kdint(nx-1,j)+kdint(nx,j))/2.d0*(dt/2.)/dx**2
|
||||
factyp=(kdint(nx,j+1)+kdint(nx,j))/2.d0*(dt/2.)/dy**2
|
||||
factym=(kdint(nx,j-1)+kdint(nx,j))/2.d0*(dt/2.)/dy**2
|
||||
diag(nx)=1.d0+factxm
|
||||
inf(nx)=-factxm
|
||||
f(nx)=zintp(nx,j)+factyp*zintp(nx,j+1)-(factyp+factym)*zintp(nx,j)+factym*zintp(nx,j-1)
|
||||
endif
|
||||
*/
|
||||
if true {
|
||||
diag[nx - 1] = 1.0;
|
||||
inf[nx - 1] = 0.0;
|
||||
f[nx - 1] = zintp[aij(nx - 1, j)];
|
||||
} else {
|
||||
// reflective boundary
|
||||
factxm = (kdint[aij(nx - 2, j)] + kdint[aij(nx - 1, j)]) / 2.0 * (dt / 2.0) / (dx * dx);
|
||||
factyp =
|
||||
(kdint[aij(nx - 1, j + 1)] + kdint[aij(nx - 1, j)]) / 2.0 * (dt / 2.0) / (dy * dy);
|
||||
factym =
|
||||
(kdint[aij(nx - 1, j - 1)] + kdint[aij(nx - 1, j)]) / 2.0 * (dt / 2.0) / (dy * dy);
|
||||
diag[nx - 1] = 1.0 + factxm;
|
||||
inf[nx - 1] = -factxm;
|
||||
f[nx - 1] = zintp[aij(nx - 1, j)] + factyp * zintp[aij(nx - 1, j + 1)]
|
||||
- (factyp + factym) * zintp[aij(nx - 1, j)]
|
||||
+ factym * zintp[aij(nx - 1, j - 1)];
|
||||
}
|
||||
/*
|
||||
call tridag (inf,diag,sup,f,res,nx)
|
||||
do i=1,nx
|
||||
zint(i,j)=res(i)
|
||||
enddo
|
||||
*/
|
||||
tridag(&inf, &diag, &sup, &f, &mut res, nx);
|
||||
for i in 0..nx {
|
||||
zint[aij(i, j)] = res[i];
|
||||
}
|
||||
/*
|
||||
enddo
|
||||
deallocate (f,diag,sup,inf,res)
|
||||
*/
|
||||
}
|
||||
|
||||
/*
|
||||
! second pass along y-axis
|
||||
|
||||
allocate (f(ny),diag(ny),sup(ny),inf(ny),res(ny))
|
||||
f=0.d0
|
||||
diag=0.d0
|
||||
sup=0.d0
|
||||
inf=0.d0
|
||||
res=0.d0
|
||||
do i=2,nx-1
|
||||
*/
|
||||
f = vec![0.0; ny];
|
||||
diag = vec![0.0; ny];
|
||||
sup = vec![0.0; ny];
|
||||
inf = vec![0.0; ny];
|
||||
res = vec![0.0; ny];
|
||||
for i in 1..nx - 1 {
|
||||
/*
|
||||
do j=2,ny-1
|
||||
factxp=(kdint(i+1,j)+kdint(i,j))/2.d0*(dt/2.)/dx**2
|
||||
factxm=(kdint(i-1,j)+kdint(i,j))/2.d0*(dt/2.)/dx**2
|
||||
factyp=(kdint(i,j+1)+kdint(i,j))/2.d0*(dt/2.)/dy**2
|
||||
factym=(kdint(i,j-1)+kdint(i,j))/2.d0*(dt/2.)/dy**2
|
||||
diag(j)=1.d0+factyp+factym
|
||||
sup(j)=-factyp
|
||||
inf(j)=-factym
|
||||
f(j)=zint(i,j)+factxp*zint(i+1,j)-(factxp+factxm)*zint(i,j)+factxm*zint(i-1,j)
|
||||
enddo
|
||||
*/
|
||||
for j in 1..ny - 1 {
|
||||
factxp = (kdint[aij(i + 1, j)] + kdint[aij(i, j)]) / 2.0 * (dt / 2.0) / (dx * dx);
|
||||
factxm = (kdint[aij(i - 1, j)] + kdint[aij(i, j)]) / 2.0 * (dt / 2.0) / (dx * dx);
|
||||
factyp = (kdint[aij(i, j + 1)] + kdint[aij(i, j)]) / 2.0 * (dt / 2.0) / (dy * dy);
|
||||
factym = (kdint[aij(i, j - 1)] + kdint[aij(i, j)]) / 2.0 * (dt / 2.0) / (dy * dy);
|
||||
diag[j] = 1.0 + factyp + factym;
|
||||
sup[j] = -factyp;
|
||||
inf[j] = -factym;
|
||||
f[j] = zint[aij(i, j)] + factxp * zint[aij(i + 1, j)]
|
||||
- (factxp + factxm) * zint[aij(i, j)]
|
||||
+ factxm * zint[aij(i - 1, j)];
|
||||
}
|
||||
/*
|
||||
! bottom bc
|
||||
if (cbc(1:1).eq.'1') then
|
||||
diag(1)=1.
|
||||
sup(1)=0.
|
||||
f(1)=zint(i,1)
|
||||
else
|
||||
factxp=(kdint(i+1,1)+kdint(i,j))/2.d0*(dt/2.)/dx**2
|
||||
factxm=(kdint(i-1,1)+kdint(i,1))/2.d0*(dt/2.)/dx**2
|
||||
factyp=(kdint(i,2)+kdint(i,1))/2.d0*(dt/2.)/dy**2
|
||||
diag(1)=1.d0+factyp
|
||||
sup(1)=-factyp
|
||||
f(1)=zint(i,1)+factxp*zint(i+1,1)-(factxp+factxm)*zint(i,1)+factxm*zint(i-1,1)
|
||||
endif
|
||||
*/
|
||||
if true {
|
||||
diag[0] = 1.0;
|
||||
sup[0] = 0.0;
|
||||
f[0] = zint[aij(i, 0)];
|
||||
} else {
|
||||
// reflective boundary
|
||||
// TODO: Check whether this j was actually supposed to be a 0 in the original
|
||||
// (probably).
|
||||
// factxp = (kdint[aij(i+1, 0)] + kdint[aij(i, j)]) / 2.0 * (dt / 2.0) / (dx * dx);
|
||||
factxp = (kdint[aij(i + 1, 0)] + kdint[aij(i, 0)]) / 2.0 * (dt / 2.0) / (dx * dx);
|
||||
factxm = (kdint[aij(i - 1, 0)] + kdint[aij(i, 0)]) / 2.0 * (dt / 2.0) / (dx * dx);
|
||||
factyp = (kdint[aij(i, 1)] + kdint[aij(i, 0)]) / 2.0 * (dt / 2.0) / (dy * dy);
|
||||
diag[0] = 1.0 + factyp;
|
||||
sup[0] = -factyp;
|
||||
f[0] = zint[aij(i, 0)] + factxp * zint[aij(i + 1, 0)]
|
||||
- (factxp + factxm) * zint[aij(i, 0)]
|
||||
+ factxm * zint[aij(i - 1, 0)];
|
||||
}
|
||||
/*
|
||||
! top bc
|
||||
if (cbc(3:3).eq.'1') then
|
||||
diag(ny)=1.
|
||||
inf(ny)=0.
|
||||
f(ny)=zint(i,ny)
|
||||
else
|
||||
factxp=(kdint(i+1,ny)+kdint(i,ny))/2.d0*(dt/2.)/dx**2
|
||||
factxm=(kdint(i-1,ny)+kdint(i,ny))/2.d0*(dt/2.)/dx**2
|
||||
factym=(kdint(i,ny-1)+kdint(i,ny))/2.d0*(dt/2.)/dy**2
|
||||
diag(ny)=1.d0+factym
|
||||
inf(ny)=-factym
|
||||
f(ny)=zint(i,ny)+factxp*zint(i+1,ny)-(factxp+factxm)*zint(i,ny)+factxm*zint(i-1,ny)
|
||||
endif
|
||||
*/
|
||||
if true {
|
||||
diag[ny - 1] = 1.0;
|
||||
inf[ny - 1] = 0.0;
|
||||
f[ny - 1] = zint[aij(i, ny - 1)];
|
||||
} else {
|
||||
// reflective boundary
|
||||
factxp =
|
||||
(kdint[aij(i + 1, ny - 1)] + kdint[aij(i, ny - 1)]) / 2.0 * (dt / 2.0) / (dx * dx);
|
||||
factxm =
|
||||
(kdint[aij(i - 1, ny - 1)] + kdint[aij(i, ny - 1)]) / 2.0 * (dt / 2.0) / (dx * dx);
|
||||
factym = (kdint[aij(i, ny - 2)] + kdint[aij(i, ny - 1)]) / 2.0 * (dt / 2.0) / (dy * dy);
|
||||
diag[ny - 1] = 1.0 + factym;
|
||||
inf[ny - 1] = -factym;
|
||||
f[ny - 1] = zint[aij(i, ny - 1)] + factxp * zint[aij(i + 1, ny - 1)]
|
||||
- (factxp + factxm) * zint[aij(i, ny - 1)]
|
||||
+ factxm * zint[aij(i - 1, ny - 1)];
|
||||
}
|
||||
/*
|
||||
call tridag (inf,diag,sup,f,res,ny)
|
||||
do j=1,ny
|
||||
zintp(i,j)=res(j)
|
||||
enddo
|
||||
*/
|
||||
tridag(&inf, &diag, &sup, &f, &mut res, ny);
|
||||
for j in 0..ny {
|
||||
zintp[aij(i, j)] = res[j];
|
||||
}
|
||||
/*
|
||||
enddo
|
||||
deallocate (f,diag,sup,inf,res)
|
||||
*/
|
||||
}
|
||||
/*
|
||||
! stores result in 1D array
|
||||
|
||||
do j=1,ny
|
||||
do i=1,nx
|
||||
ij=(j-1)*nx+i
|
||||
etot(ij)=etot(ij)+h(ij)-zintp(i,j)
|
||||
erate(ij)=erate(ij)+(h(ij)-zintp(i,j))/dt
|
||||
h(ij)=zintp(i,j)
|
||||
enddo
|
||||
enddo
|
||||
|
||||
b=min(h,b)
|
||||
*/
|
||||
for j in 0..ny {
|
||||
for i in 0..nx {
|
||||
ij = aij(i, j);
|
||||
// FIXME: Track total erosion and erosion rate.
|
||||
h[ij] = zintp[ij] as Alt;
|
||||
}
|
||||
}
|
||||
|
||||
b.par_iter_mut().zip(h).for_each(|(b, h)| {
|
||||
*b = h.min(*b);
|
||||
});
|
||||
/*
|
||||
deallocate (zint,kdint,zintp)
|
||||
|
||||
return
|
||||
|
||||
end subroutine Diffusion
|
||||
*/
|
||||
}
|
||||
|
||||
/*
|
||||
!----------
|
||||
|
||||
! subroutine to solve a tri-diagonal system of equations (from Numerical Recipes)
|
||||
|
||||
SUBROUTINE tridag(a,b,c,r,u,n)
|
||||
|
||||
implicit none
|
||||
|
||||
INTEGER n
|
||||
double precision a(n),b(n),c(n),r(n),u(n)
|
||||
*/
|
||||
pub fn tridag(a: &[f64], b: &[f64], c: &[f64], r: &[f64], u: &mut [f64], n: usize) {
|
||||
/*
|
||||
INTEGER j
|
||||
double precision bet
|
||||
double precision,dimension(:),allocatable::gam
|
||||
|
||||
allocate (gam(n))
|
||||
|
||||
if(b(1).eq.0.d0) stop 'in tridag'
|
||||
*/
|
||||
let mut bet: f64;
|
||||
let mut gam: Vec<f64>;
|
||||
|
||||
gam = vec![Default::default(); n];
|
||||
|
||||
assert!(b[0] != 0.0);
|
||||
/*
|
||||
|
||||
! first pass
|
||||
|
||||
bet=b(1)
|
||||
u(1)=r(1)/bet
|
||||
do 11 j=2,n
|
||||
gam(j)=c(j-1)/bet
|
||||
bet=b(j)-a(j)*gam(j)
|
||||
if(bet.eq.0.) then
|
||||
print*,'tridag failed'
|
||||
stop
|
||||
endif
|
||||
u(j)=(r(j)-a(j)*u(j-1))/bet
|
||||
11 continue
|
||||
*/
|
||||
bet = b[0];
|
||||
u[0] = r[0] / bet;
|
||||
for j in 1..n {
|
||||
gam[j] = c[j - 1] / bet;
|
||||
bet = b[j] - a[j] * gam[j];
|
||||
assert!(bet != 0.0);
|
||||
// Round 0: u[0] = r[0] / b[0]
|
||||
// = r'[0] / b'[0]
|
||||
// Round j: u[j] = (r[j] - a[j] * u'[j - 1]) / b'[j]
|
||||
// = (r[j] - a[j] * r'[j - 1] / b'[j - 1]) / b'[j]
|
||||
// = (r[j] - (a[j] / b'[j - 1]) * r'[j - 1]) / b'[j]
|
||||
// = (r[j] - w[j] * r'[j - 1]) / b'[j]
|
||||
// = r'[j] / b'[j]
|
||||
u[j] = (r[j] - a[j] * u[j - 1]) / bet;
|
||||
}
|
||||
/*
|
||||
! second pass
|
||||
|
||||
do 12 j=n-1,1,-1
|
||||
u(j)=u(j)-gam(j+1)*u(j+1)
|
||||
12 continue
|
||||
*/
|
||||
for j in (0..n - 1).rev() {
|
||||
u[j] = u[j] - gam[j + 1] * u[j + 1];
|
||||
}
|
||||
/*
|
||||
deallocate (gam)
|
||||
|
||||
return
|
||||
|
||||
END
|
||||
*/
|
||||
}
|
File diff suppressed because it is too large
Load Diff
289
world/src/sim/map.rs
Normal file
289
world/src/sim/map.rs
Normal file
@ -0,0 +1,289 @@
|
||||
use crate::{
|
||||
sim::{RiverKind, WorldSim, WORLD_SIZE},
|
||||
CONFIG,
|
||||
};
|
||||
use common::{terrain::TerrainChunkSize, vol::RectVolSize};
|
||||
use std::{f32, f64};
|
||||
use vek::*;
|
||||
|
||||
pub struct MapConfig {
|
||||
/// Dimensions of the window being written to. Defaults to WORLD_SIZE.
|
||||
pub dimensions: Vec2<usize>,
|
||||
/// x, y, and z of top left of map (defaults to (0.0, 0.0, CONFIG.sea_level)).
|
||||
pub focus: Vec3<f64>,
|
||||
/// Altitude is divided by gain and clamped to [0, 1]; thus, decreasing gain makes
|
||||
/// smaller differences in altitude appear larger.
|
||||
///
|
||||
/// Defaults to CONFIG.mountain_scale.
|
||||
pub gain: f32,
|
||||
/// lgain is used for shading purposes and refers to how much impact a change in the z
|
||||
/// direction has on the perceived slope relative to the same change in x and y.
|
||||
///
|
||||
/// Defaults to TerrainChunkSize::RECT_SIZE.x.
|
||||
pub lgain: f64,
|
||||
/// Scale is like gain, but for x and y rather than z.
|
||||
///
|
||||
/// Defaults to WORLD_SIZE.x / dimensions.x (NOTE: fractional, not integer, division!).
|
||||
pub scale: f64,
|
||||
/// Vector that indicates which direction light is coming from, if shading is turned on.
|
||||
///
|
||||
/// Right-handed coordinate system: light is going left, down, and "backwards" (i.e. on the
|
||||
/// map, where we translate the y coordinate on the world map to z in the coordinate system,
|
||||
/// the light comes from -y on the map and points towards +y on the map). In a right
|
||||
/// handed coordinate system, the "camera" points towards -z, so positive z is backwards
|
||||
/// "into" the camera.
|
||||
///
|
||||
/// "In world space the x-axis will be pointing east, the y-axis up and the z-axis will be pointing south"
|
||||
///
|
||||
/// Defaults to (-0.8, -1.0, 0.3).
|
||||
pub light_direction: Vec3<f64>,
|
||||
/// If true, only the basement (bedrock) is used for altitude; otherwise, the surface is used.
|
||||
///
|
||||
/// Defaults to false.
|
||||
pub is_basement: bool,
|
||||
/// If true, water is rendered; otherwise, the surface without water is rendered, even if it
|
||||
/// is underwater.
|
||||
///
|
||||
/// Defaults to true.
|
||||
pub is_water: bool,
|
||||
/// If true, 3D lighting and shading are turned on. Otherwise, a plain altitude map is used.
|
||||
///
|
||||
/// Defaults to true.
|
||||
pub is_shaded: bool,
|
||||
/// If true, the red component of the image is also used for temperature (redder is hotter).
|
||||
/// Defaults to false.
|
||||
pub is_temperature: bool,
|
||||
/// If true, the blue component of the image is also used for humidity (bluer is wetter).
|
||||
///
|
||||
/// Defaults to false.
|
||||
pub is_humidity: bool,
|
||||
/// Record debug information.
|
||||
///
|
||||
/// Defaults to false.
|
||||
pub is_debug: bool,
|
||||
}
|
||||
|
||||
pub const QUADRANTS: usize = 4;
|
||||
|
||||
pub struct MapDebug {
|
||||
pub quads: [[u32; QUADRANTS]; QUADRANTS],
|
||||
pub rivers: u32,
|
||||
pub lakes: u32,
|
||||
pub oceans: u32,
|
||||
}
|
||||
|
||||
impl Default for MapConfig {
|
||||
fn default() -> Self {
|
||||
let dimensions = WORLD_SIZE;
|
||||
Self {
|
||||
dimensions,
|
||||
focus: Vec3::new(0.0, 0.0, CONFIG.sea_level as f64),
|
||||
gain: CONFIG.mountain_scale,
|
||||
lgain: TerrainChunkSize::RECT_SIZE.x as f64,
|
||||
scale: WORLD_SIZE.x as f64 / dimensions.x as f64,
|
||||
light_direction: Vec3::new(-0.8, -1.0, 0.3),
|
||||
|
||||
is_basement: false,
|
||||
is_water: true,
|
||||
is_shaded: true,
|
||||
is_temperature: false,
|
||||
is_humidity: false,
|
||||
is_debug: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MapConfig {
|
||||
/// Generates a map image using the specified settings. Note that it will write from left to
|
||||
/// write from (0, 0) to dimensions - 1, inclusive, with 4 1-byte color components provided
|
||||
/// as (r, g, b, a). It is up to the caller to provide a function that translates this
|
||||
/// information into the correct format for a buffer and writes to it.
|
||||
pub fn generate(
|
||||
&self,
|
||||
sampler: &WorldSim,
|
||||
mut write_pixel: impl FnMut(Vec2<usize>, (u8, u8, u8, u8)),
|
||||
) -> MapDebug {
|
||||
let MapConfig {
|
||||
dimensions,
|
||||
focus,
|
||||
gain,
|
||||
lgain,
|
||||
scale,
|
||||
light_direction,
|
||||
|
||||
is_basement,
|
||||
is_water,
|
||||
is_shaded,
|
||||
is_temperature,
|
||||
is_humidity,
|
||||
is_debug,
|
||||
} = *self;
|
||||
|
||||
let light = light_direction.normalized();
|
||||
let mut quads = [[0u32; QUADRANTS]; QUADRANTS];
|
||||
let mut rivers = 0u32;
|
||||
let mut lakes = 0u32;
|
||||
let mut oceans = 0u32;
|
||||
|
||||
let focus_rect = Vec2::from(focus);
|
||||
let true_sea_level = (CONFIG.sea_level as f64 - focus.z) / gain as f64;
|
||||
|
||||
(0..dimensions.y * dimensions.x)
|
||||
.into_iter()
|
||||
.for_each(|chunk_idx| {
|
||||
let i = chunk_idx % dimensions.x as usize;
|
||||
let j = chunk_idx / dimensions.x as usize;
|
||||
|
||||
let pos =
|
||||
(focus_rect + Vec2::new(i as f64, j as f64) * scale).map(|e: f64| e as i32);
|
||||
|
||||
let (alt, basement, water_alt, humidity, temperature, downhill, river_kind) =
|
||||
sampler
|
||||
.get(pos)
|
||||
.map(|sample| {
|
||||
(
|
||||
sample.alt,
|
||||
sample.basement,
|
||||
sample.water_alt,
|
||||
sample.humidity,
|
||||
sample.temp,
|
||||
sample.downhill,
|
||||
sample.river.river_kind,
|
||||
)
|
||||
})
|
||||
.unwrap_or((
|
||||
CONFIG.sea_level,
|
||||
CONFIG.sea_level,
|
||||
CONFIG.sea_level,
|
||||
0.0,
|
||||
0.0,
|
||||
None,
|
||||
None,
|
||||
));
|
||||
let humidity = humidity.min(1.0).max(0.0);
|
||||
let temperature = temperature.min(1.0).max(-1.0) * 0.5 + 0.5;
|
||||
let pos = pos * TerrainChunkSize::RECT_SIZE.map(|e| e as i32);
|
||||
let downhill_pos = (downhill
|
||||
.map(|downhill_pos| downhill_pos)
|
||||
.unwrap_or(pos + TerrainChunkSize::RECT_SIZE.map(|e| e as i32))
|
||||
- pos)
|
||||
+ pos;
|
||||
let downhill_alt = sampler
|
||||
.get_wpos(downhill_pos)
|
||||
.map(|s| if is_basement { s.basement } else { s.alt })
|
||||
.unwrap_or(CONFIG.sea_level);
|
||||
let alt = if is_basement { basement } else { alt };
|
||||
let cross_pos = pos
|
||||
+ ((downhill_pos - pos)
|
||||
.map(|e| e as f32)
|
||||
.rotated_z(f32::consts::FRAC_PI_2)
|
||||
.map(|e| e as i32));
|
||||
let cross_alt = sampler
|
||||
.get_wpos(cross_pos)
|
||||
.map(|s| if is_basement { s.basement } else { s.alt })
|
||||
.unwrap_or(CONFIG.sea_level);
|
||||
// Pointing downhill, forward
|
||||
// (index--note that (0,0,1) is backward right-handed)
|
||||
let forward_vec = Vec3::new(
|
||||
(downhill_pos.x - pos.x) as f64,
|
||||
(downhill_alt - alt) as f64 * lgain,
|
||||
(downhill_pos.y - pos.y) as f64,
|
||||
);
|
||||
// Pointing 90 degrees left (in horizontal xy) of downhill, up
|
||||
// (middle--note that (1,0,0), 90 degrees CCW backward, is right right-handed)
|
||||
let up_vec = Vec3::new(
|
||||
(cross_pos.x - pos.x) as f64,
|
||||
(cross_alt - alt) as f64 * lgain,
|
||||
(cross_pos.y - pos.y) as f64,
|
||||
);
|
||||
// Then cross points "to the right" (upwards) on a right-handed coordinate system.
|
||||
// (right-handed coordinate system means (0, 0, 1.0) is "forward" into the screen).
|
||||
let surface_normal = forward_vec.cross(up_vec).normalized();
|
||||
let light = (surface_normal.dot(light) + 1.0) / 2.0;
|
||||
let light = (light * 0.9) + 0.1;
|
||||
|
||||
let true_water_alt = (alt.max(water_alt) as f64 - focus.z) / gain as f64;
|
||||
let true_alt = (alt as f64 - focus.z) / gain as f64;
|
||||
let water_depth = (true_water_alt - true_alt).min(1.0).max(0.0);
|
||||
let water_alt = true_water_alt.min(1.0).max(0.0);
|
||||
let alt = true_alt.min(1.0).max(0.0);
|
||||
if is_debug {
|
||||
let quad = |x: f32| {
|
||||
((x as f64 * QUADRANTS as f64).floor() as usize).min(QUADRANTS - 1)
|
||||
};
|
||||
if river_kind.is_none() || humidity != 0.0 {
|
||||
quads[quad(humidity)][quad(temperature)] += 1;
|
||||
}
|
||||
match river_kind {
|
||||
Some(RiverKind::River { .. }) => {
|
||||
rivers += 1;
|
||||
}
|
||||
Some(RiverKind::Lake { .. }) => {
|
||||
lakes += 1;
|
||||
}
|
||||
Some(RiverKind::Ocean { .. }) => {
|
||||
oceans += 1;
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
|
||||
let rgba = match (river_kind, (is_water, true_alt >= true_sea_level)) {
|
||||
(_, (false, _)) | (None, (_, true)) => {
|
||||
let (r, g, b) = (
|
||||
(if is_shaded { alt } else { alt }
|
||||
* if is_temperature {
|
||||
temperature as f64
|
||||
} else if is_shaded {
|
||||
alt
|
||||
} else {
|
||||
0.0
|
||||
})
|
||||
.sqrt(),
|
||||
if is_shaded { 0.2 + (alt * 0.8) } else { alt },
|
||||
(if is_shaded { alt } else { alt }
|
||||
* if is_humidity {
|
||||
humidity as f64
|
||||
} else if is_shaded {
|
||||
alt
|
||||
} else {
|
||||
0.0
|
||||
})
|
||||
.sqrt(),
|
||||
);
|
||||
let light = if is_shaded { light } else { 1.0 };
|
||||
(
|
||||
(r * light * 255.0) as u8,
|
||||
(g * light * 255.0) as u8,
|
||||
(b * light * 255.0) as u8,
|
||||
255,
|
||||
)
|
||||
}
|
||||
(Some(RiverKind::Ocean), _) => (
|
||||
0,
|
||||
((32.0 - water_depth * 32.0) * 1.0) as u8,
|
||||
((64.0 - water_depth * 64.0) * 1.0) as u8,
|
||||
255,
|
||||
),
|
||||
(Some(RiverKind::River { .. }), _) => {
|
||||
(0, 32 + (alt * 95.0) as u8, 64 + (alt * 191.0) as u8, 255)
|
||||
}
|
||||
(None, _) | (Some(RiverKind::Lake { .. }), _) => (
|
||||
0,
|
||||
(((32.0 + water_alt * 95.0) + (-water_depth * 32.0)) * 1.0) as u8,
|
||||
(((64.0 + water_alt * 191.0) + (-water_depth * 64.0)) * 1.0) as u8,
|
||||
255,
|
||||
),
|
||||
};
|
||||
|
||||
write_pixel(Vec2::new(i, j), rgba);
|
||||
});
|
||||
|
||||
MapDebug {
|
||||
quads,
|
||||
rivers,
|
||||
lakes,
|
||||
oceans,
|
||||
}
|
||||
}
|
||||
}
|
1192
world/src/sim/mod.rs
1192
world/src/sim/mod.rs
File diff suppressed because it is too large
Load Diff
@ -1,9 +1,10 @@
|
||||
use super::WORLD_SIZE;
|
||||
use bitvec::prelude::{bitbox, bitvec, BitBox};
|
||||
use common::{terrain::TerrainChunkSize, vol::RectVolSize};
|
||||
use noise::{MultiFractal, NoiseFn, Perlin, Point2, Point3, Point4, Seedable};
|
||||
use num::Float;
|
||||
use rayon::prelude::*;
|
||||
use std::{f32, f64, u32};
|
||||
use std::{f32, f64, ops::Mul, u32};
|
||||
use vek::*;
|
||||
|
||||
/// Calculates the smallest distance along an axis (x, y) from an edge of
|
||||
@ -14,7 +15,7 @@ use vek::*;
|
||||
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
|
||||
(sz / 2 - (e - sz / 2).abs()) as f32 / (16.0 / 1024.0 * sz as f32)
|
||||
})
|
||||
.reduce_partial_min()
|
||||
.max(0.0)
|
||||
@ -74,13 +75,13 @@ pub fn cdf_irwin_hall<const N: usize>(weights: &[f32; N], samples: [f32; N]) ->
|
||||
// We should be able to iterate through the whole power set
|
||||
// instead, and figure out K by calling count_ones(), so we can compute the result in O(2^N)
|
||||
// iterations.
|
||||
let x: f32 = weights
|
||||
let x: f64 = weights
|
||||
.iter()
|
||||
.zip(samples.iter())
|
||||
.map(|(weight, sample)| weight * sample)
|
||||
.map(|(&weight, &sample)| weight as f64 * sample as f64)
|
||||
.sum();
|
||||
|
||||
let mut y = 0.0f32;
|
||||
let mut y = 0.0f64;
|
||||
for subset in 0u32..(1 << N) {
|
||||
// Number of set elements
|
||||
let k = subset.count_ones();
|
||||
@ -89,8 +90,8 @@ pub fn cdf_irwin_hall<const N: usize>(weights: &[f32; N], samples: [f32; N]) ->
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(i, _)| subset & (1 << i) as u32 != 0)
|
||||
.map(|(_, k)| k)
|
||||
.sum::<f32>();
|
||||
.map(|(_, &k)| k as f64)
|
||||
.sum::<f64>();
|
||||
// Compute max(0, x - B_subset)^N
|
||||
let z = (x - z).max(0.0).powi(N as i32);
|
||||
// The parity of k determines whether the sum is negated.
|
||||
@ -98,10 +99,10 @@ pub fn cdf_irwin_hall<const N: usize>(weights: &[f32; N], samples: [f32; N]) ->
|
||||
}
|
||||
|
||||
// Divide by the product of the weights.
|
||||
y /= weights.iter().product::<f32>();
|
||||
y /= weights.iter().map(|&k| k as f64).product::<f64>();
|
||||
|
||||
// Remember to multiply by 1 / N! at the end.
|
||||
y / (1..(N as i32) + 1).product::<i32>() as f32
|
||||
(y / (1..(N as i32) + 1).product::<i32>() as f64) as f32
|
||||
}
|
||||
|
||||
/// First component of each element of the vector is the computed CDF of the noise function at this
|
||||
@ -148,7 +149,7 @@ pub fn vec2_as_uniform_idx(idx: Vec2<i32>) -> usize {
|
||||
/// vector returned by uniform_noise, and (for convenience) the float-translated version of those
|
||||
/// coordinates.
|
||||
/// f should return a value with no NaNs. If there is a NaN, it will panic. There are no other
|
||||
/// conditions on f. If f returns None, the value will be set to 0.0, and will be ignored for the
|
||||
/// conditions on f. If f returns None, the value will be set to NaN, and will be ignored for the
|
||||
/// purposes of computing the uniform range.
|
||||
///
|
||||
/// Returns a vec of (f32, f32) pairs consisting of the percentage of chunks with a value lower than
|
||||
@ -179,7 +180,7 @@ pub fn uniform_noise<F: Float + Send>(
|
||||
// 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, F::zero()); WORLD_SIZE.x * WORLD_SIZE.y].into_boxed_slice();
|
||||
let mut uniform_noise = vec![(0.0, F::nan()); 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;
|
||||
@ -215,26 +216,28 @@ pub fn local_cells(posi: usize) -> impl Clone + Iterator<Item = usize> {
|
||||
.map(vec2_as_uniform_idx)
|
||||
}
|
||||
|
||||
// NOTE: want to keep this such that the chunk index is in ascending order!
|
||||
pub const NEIGHBOR_DELTA: [(i32, i32); 8] = [
|
||||
(-1, -1),
|
||||
(0, -1),
|
||||
(1, -1),
|
||||
(-1, 0),
|
||||
(1, 0),
|
||||
(-1, 1),
|
||||
(0, 1),
|
||||
(1, 1),
|
||||
];
|
||||
|
||||
/// Iterate through all cells adjacent to a chunk.
|
||||
pub fn neighbors(posi: usize) -> impl Clone + Iterator<Item = usize> {
|
||||
let pos = uniform_idx_as_vec2(posi);
|
||||
// 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),
|
||||
]
|
||||
.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)
|
||||
NEIGHBOR_DELTA
|
||||
.iter()
|
||||
.map(move |&(x, y)| Vec2::new(pos.x + x, pos.y + y))
|
||||
.filter(|pos| {
|
||||
pos.x >= 0 && pos.y >= 0 && pos.x < WORLD_SIZE.x as i32 && pos.y < WORLD_SIZE.y as i32
|
||||
})
|
||||
.map(vec2_as_uniform_idx)
|
||||
}
|
||||
|
||||
// Note that we should already have okay cache locality since we have a grid.
|
||||
@ -245,20 +248,23 @@ pub fn uphill<'a>(dh: &'a [isize], posi: usize) -> impl Clone + Iterator<Item =
|
||||
/// 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]> {
|
||||
pub fn downhill<F: Float>(
|
||||
h: impl Fn(usize) -> F + Sync,
|
||||
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);
|
||||
(0..WORLD_SIZE.x * WORLD_SIZE.y)
|
||||
.into_par_iter()
|
||||
.map(|posi| {
|
||||
let nh = h(posi);
|
||||
if is_ocean(posi) {
|
||||
-2
|
||||
} else {
|
||||
let mut best = -1;
|
||||
let mut besth = nh;
|
||||
for nposi in neighbors(posi) {
|
||||
let nbh = h[nposi];
|
||||
let nbh = h(nposi);
|
||||
if nbh < besth {
|
||||
besth = nbh;
|
||||
best = nposi as isize;
|
||||
@ -274,18 +280,24 @@ pub fn downhill(h: &[f32], is_ocean: impl Fn(usize) -> bool + Sync) -> Box<[isiz
|
||||
/// 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 {
|
||||
pub fn get_oceans<F: Float>(oldh: impl Fn(usize) -> F + 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();
|
||||
let mut do_push = |pos| {
|
||||
let posi = vec2_as_uniform_idx(pos);
|
||||
if oldh(posi) <= F::zero() {
|
||||
stack.push(posi);
|
||||
}
|
||||
};
|
||||
for x in 0..WORLD_SIZE.x as i32 {
|
||||
stack.push(vec2_as_uniform_idx(Vec2::new(x, 0)));
|
||||
stack.push(vec2_as_uniform_idx(Vec2::new(x, WORLD_SIZE.y as i32 - 1)));
|
||||
do_push(Vec2::new(x, 0));
|
||||
do_push(Vec2::new(x, WORLD_SIZE.y as i32 - 1));
|
||||
}
|
||||
for y in 1..WORLD_SIZE.y as i32 - 1 {
|
||||
stack.push(vec2_as_uniform_idx(Vec2::new(0, y)));
|
||||
stack.push(vec2_as_uniform_idx(Vec2::new(WORLD_SIZE.x as i32 - 1, y)));
|
||||
do_push(Vec2::new(0, y));
|
||||
do_push(Vec2::new(WORLD_SIZE.x as i32 - 1, y));
|
||||
}
|
||||
while let Some(chunk_idx) = stack.pop() {
|
||||
// println!("Ocean chunk {:?}: {:?}", uniform_idx_as_vec2(chunk_idx), oldh(chunk_idx));
|
||||
@ -295,8 +307,397 @@ pub fn get_oceans(oldh: impl Fn(usize) -> f32 + Sync) -> BitBox {
|
||||
*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
|
||||
oldh(neighbor_idx) <= F::zero()
|
||||
}));
|
||||
}
|
||||
is_ocean
|
||||
}
|
||||
|
||||
/// A 2-dimensional vector, for internal use.
|
||||
type Vector2<T> = [T; 2];
|
||||
/// A 3-dimensional vector, for internal use.
|
||||
type Vector3<T> = [T; 3];
|
||||
/// A 4-dimensional vector, for internal use.
|
||||
type Vector4<T> = [T; 4];
|
||||
|
||||
#[inline]
|
||||
fn zip_with2<T, U, V, F>(a: Vector2<T>, b: Vector2<U>, f: F) -> Vector2<V>
|
||||
where
|
||||
T: Copy,
|
||||
U: Copy,
|
||||
F: Fn(T, U) -> V,
|
||||
{
|
||||
let (ax, ay) = (a[0], a[1]);
|
||||
let (bx, by) = (b[0], b[1]);
|
||||
[f(ax, bx), f(ay, by)]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn zip_with3<T, U, V, F>(a: Vector3<T>, b: Vector3<U>, f: F) -> Vector3<V>
|
||||
where
|
||||
T: Copy,
|
||||
U: Copy,
|
||||
F: Fn(T, U) -> V,
|
||||
{
|
||||
let (ax, ay, az) = (a[0], a[1], a[2]);
|
||||
let (bx, by, bz) = (b[0], b[1], b[2]);
|
||||
[f(ax, bx), f(ay, by), f(az, bz)]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn zip_with4<T, U, V, F>(a: Vector4<T>, b: Vector4<U>, f: F) -> Vector4<V>
|
||||
where
|
||||
T: Copy,
|
||||
U: Copy,
|
||||
F: Fn(T, U) -> V,
|
||||
{
|
||||
let (ax, ay, az, aw) = (a[0], a[1], a[2], a[3]);
|
||||
let (bx, by, bz, bw) = (b[0], b[1], b[2], b[3]);
|
||||
[f(ax, bx), f(ay, by), f(az, bz), f(aw, bw)]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn mul2<T>(a: Vector2<T>, b: T) -> Vector2<T>
|
||||
where
|
||||
T: Copy + Mul<T, Output = T>,
|
||||
{
|
||||
zip_with2(a, const2(b), Mul::mul)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn mul3<T>(a: Vector3<T>, b: T) -> Vector3<T>
|
||||
where
|
||||
T: Copy + Mul<T, Output = T>,
|
||||
{
|
||||
zip_with3(a, const3(b), Mul::mul)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn mul4<T>(a: Vector4<T>, b: T) -> Vector4<T>
|
||||
where
|
||||
T: Copy + Mul<T, Output = T>,
|
||||
{
|
||||
zip_with4(a, const4(b), Mul::mul)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn const2<T: Copy>(x: T) -> Vector2<T> {
|
||||
[x, x]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn const3<T: Copy>(x: T) -> Vector3<T> {
|
||||
[x, x, x]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn const4<T: Copy>(x: T) -> Vector4<T> {
|
||||
[x, x, x, x]
|
||||
}
|
||||
|
||||
fn build_sources(seed: u32, octaves: usize) -> Vec<Perlin> {
|
||||
let mut sources = Vec::with_capacity(octaves);
|
||||
for x in 0..octaves {
|
||||
sources.push(Perlin::new().set_seed(seed + x as u32));
|
||||
}
|
||||
sources
|
||||
}
|
||||
|
||||
/// Noise function that outputs hybrid Multifractal noise.
|
||||
///
|
||||
/// The result of this multifractal noise is that valleys in the noise should
|
||||
/// have smooth bottoms at all altitudes.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct HybridMulti {
|
||||
/// Total number of frequency octaves to generate the noise with.
|
||||
///
|
||||
/// The number of octaves control the _amount of detail_ in the noise
|
||||
/// function. Adding more octaves increases the detail, with the drawback
|
||||
/// of increasing the calculation time.
|
||||
pub octaves: usize,
|
||||
|
||||
/// The number of cycles per unit length that the noise function outputs.
|
||||
pub frequency: f64,
|
||||
|
||||
/// A multiplier that determines how quickly the frequency increases for
|
||||
/// each successive octave in the noise function.
|
||||
///
|
||||
/// The frequency of each successive octave is equal to the product of the
|
||||
/// previous octave's frequency and the lacunarity value.
|
||||
///
|
||||
/// A lacunarity of 2.0 results in the frequency doubling every octave. For
|
||||
/// almost all cases, 2.0 is a good value to use.
|
||||
pub lacunarity: f64,
|
||||
|
||||
/// A multiplier that determines how quickly the amplitudes diminish for
|
||||
/// each successive octave in the noise function.
|
||||
///
|
||||
/// The amplitude of each successive octave is equal to the product of the
|
||||
/// previous octave's amplitude and the persistence value. Increasing the
|
||||
/// persistence produces "rougher" noise.
|
||||
///
|
||||
/// H = 1.0 - fractal increment = -ln(persistence) / ln(lacunarity). For
|
||||
/// a fractal increment between 0 (inclusive) and 1 (exclusive), keep
|
||||
/// persistence between 1 / lacunarity (inclusive, for low fractal
|
||||
/// dimension) and 1 (exclusive, for high fractal dimension).
|
||||
pub persistence: f64,
|
||||
|
||||
/// An offset that is added to the output of each sample of the underlying
|
||||
/// Perlin noise function. Because each successive octave is weighted in
|
||||
/// part by the previous signal's output, increasing the offset will weight
|
||||
/// the output more heavily towards 1.0.
|
||||
pub offset: f64,
|
||||
|
||||
seed: u32,
|
||||
sources: Vec<Perlin>,
|
||||
}
|
||||
|
||||
impl HybridMulti {
|
||||
pub const DEFAULT_SEED: u32 = 0;
|
||||
pub const DEFAULT_OCTAVES: usize = 6;
|
||||
pub const DEFAULT_FREQUENCY: f64 = 2.0;
|
||||
pub const DEFAULT_LACUNARITY: f64 = /* std::f64::consts::PI * 2.0 / 3.0 */2.0;
|
||||
// -ln(2^(-0.25))/ln(2) = 0.25
|
||||
// 2^(-0.25) ~ 13/16
|
||||
pub const DEFAULT_PERSISTENCE: f64 = /* 0.25 *//* 0.5*/ 13.0 / 16.0;
|
||||
pub const DEFAULT_OFFSET: f64 = /* 0.25 *//* 0.5*/ 0.7;
|
||||
pub const MAX_OCTAVES: usize = 32;
|
||||
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
seed: Self::DEFAULT_SEED,
|
||||
octaves: Self::DEFAULT_OCTAVES,
|
||||
frequency: Self::DEFAULT_FREQUENCY,
|
||||
lacunarity: Self::DEFAULT_LACUNARITY,
|
||||
persistence: Self::DEFAULT_PERSISTENCE,
|
||||
offset: Self::DEFAULT_OFFSET,
|
||||
sources: build_sources(Self::DEFAULT_SEED, Self::DEFAULT_OCTAVES),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_offset(self, offset: f64) -> Self {
|
||||
Self { offset, ..self }
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for HybridMulti {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl MultiFractal for HybridMulti {
|
||||
fn set_octaves(self, mut octaves: usize) -> Self {
|
||||
if self.octaves == octaves {
|
||||
return self;
|
||||
}
|
||||
|
||||
octaves = octaves.max(1).min(Self::MAX_OCTAVES);
|
||||
Self {
|
||||
octaves,
|
||||
sources: build_sources(self.seed, octaves),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
fn set_frequency(self, frequency: f64) -> Self {
|
||||
Self { frequency, ..self }
|
||||
}
|
||||
|
||||
fn set_lacunarity(self, lacunarity: f64) -> Self {
|
||||
Self { lacunarity, ..self }
|
||||
}
|
||||
|
||||
fn set_persistence(self, persistence: f64) -> Self {
|
||||
Self {
|
||||
persistence,
|
||||
..self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Seedable for HybridMulti {
|
||||
fn set_seed(self, seed: u32) -> Self {
|
||||
if self.seed == seed {
|
||||
return self;
|
||||
}
|
||||
|
||||
Self {
|
||||
seed,
|
||||
sources: build_sources(seed, self.octaves),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
fn seed(&self) -> u32 {
|
||||
self.seed
|
||||
}
|
||||
}
|
||||
|
||||
/// 2-dimensional `HybridMulti` noise
|
||||
impl NoiseFn<Point2<f64>> for HybridMulti {
|
||||
fn get(&self, mut point: Point2<f64>) -> f64 {
|
||||
// First unscaled octave of function; later octaves are scaled.
|
||||
point = mul2(point, self.frequency);
|
||||
// Offset and bias to scale into [offset - 1.0, 1.0 + offset] range.
|
||||
let bias = 1.0;
|
||||
let mut result = (self.sources[0].get(point) + self.offset) * bias * self.persistence;
|
||||
let mut exp_scale = 1.0;
|
||||
let mut scale = self.persistence;
|
||||
let mut weight = result;
|
||||
|
||||
// Spectral construction inner loop, where the fractal is built.
|
||||
for x in 1..self.octaves {
|
||||
// Prevent divergence.
|
||||
weight = weight.min(1.0);
|
||||
|
||||
// Raise the spatial frequency.
|
||||
point = mul2(point, self.lacunarity);
|
||||
|
||||
// Get noise value, and scale it to the [offset - 1.0, 1.0 + offset] range.
|
||||
let mut signal = (self.sources[x].get(point) + self.offset) * bias;
|
||||
|
||||
// Scale the amplitude appropriately for this frequency.
|
||||
exp_scale *= self.persistence;
|
||||
signal *= exp_scale;
|
||||
|
||||
// Add it in, weighted by previous octave's noise value.
|
||||
result += weight * signal;
|
||||
|
||||
// Update the weighting value.
|
||||
weight *= signal;
|
||||
scale += exp_scale;
|
||||
}
|
||||
|
||||
// Scale the result to the [-1,1] range
|
||||
(result / scale) / bias - self.offset
|
||||
}
|
||||
}
|
||||
|
||||
/// 3-dimensional `HybridMulti` noise
|
||||
impl NoiseFn<Point3<f64>> for HybridMulti {
|
||||
fn get(&self, mut point: Point3<f64>) -> f64 {
|
||||
// First unscaled octave of function; later octaves are scaled.
|
||||
point = mul3(point, self.frequency);
|
||||
// Offset and bias to scale into [offset - 1.0, 1.0 + offset] range.
|
||||
let bias = 1.0;
|
||||
let mut result = (self.sources[0].get(point) + self.offset) * bias * self.persistence;
|
||||
let mut exp_scale = 1.0;
|
||||
let mut scale = self.persistence;
|
||||
let mut weight = result;
|
||||
|
||||
// Spectral construction inner loop, where the fractal is built.
|
||||
for x in 1..self.octaves {
|
||||
// Prevent divergence.
|
||||
weight = weight.min(1.0);
|
||||
|
||||
// Raise the spatial frequency.
|
||||
point = mul3(point, self.lacunarity);
|
||||
|
||||
// Get noise value, and scale it to the [0, 1.0] range.
|
||||
let mut signal = (self.sources[x].get(point) + self.offset) * bias;
|
||||
|
||||
// Scale the amplitude appropriately for this frequency.
|
||||
exp_scale *= self.persistence;
|
||||
signal *= exp_scale;
|
||||
|
||||
// Add it in, weighted by previous octave's noise value.
|
||||
result += weight * signal;
|
||||
|
||||
// Update the weighting value.
|
||||
weight *= signal;
|
||||
scale += exp_scale;
|
||||
}
|
||||
|
||||
// Scale the result to the [-1,1] range
|
||||
(result / scale) / bias - self.offset
|
||||
}
|
||||
}
|
||||
|
||||
/// 4-dimensional `HybridMulti` noise
|
||||
impl NoiseFn<Point4<f64>> for HybridMulti {
|
||||
fn get(&self, mut point: Point4<f64>) -> f64 {
|
||||
// First unscaled octave of function; later octaves are scaled.
|
||||
point = mul4(point, self.frequency);
|
||||
// Offset and bias to scale into [offset - 1.0, 1.0 + offset] range.
|
||||
let bias = 1.0;
|
||||
let mut result = (self.sources[0].get(point) + self.offset) * bias * self.persistence;
|
||||
let mut exp_scale = 1.0;
|
||||
let mut scale = self.persistence;
|
||||
let mut weight = result;
|
||||
|
||||
// Spectral construction inner loop, where the fractal is built.
|
||||
for x in 1..self.octaves {
|
||||
// Prevent divergence.
|
||||
weight = weight.min(1.0);
|
||||
|
||||
// Raise the spatial frequency.
|
||||
point = mul4(point, self.lacunarity);
|
||||
|
||||
// Get noise value, and scale it to the [0, 1.0] range.
|
||||
let mut signal = (self.sources[x].get(point) + self.offset) * bias;
|
||||
|
||||
// Scale the amplitude appropriately for this frequency.
|
||||
exp_scale *= self.persistence;
|
||||
signal *= exp_scale;
|
||||
|
||||
// Add it in, weighted by previous octave's noise value.
|
||||
result += weight * signal;
|
||||
|
||||
// Update the weighting value.
|
||||
weight *= signal;
|
||||
scale += exp_scale;
|
||||
}
|
||||
|
||||
// Scale the result to the [-1,1] range
|
||||
(result / scale) / bias - self.offset
|
||||
}
|
||||
}
|
||||
|
||||
/// Noise function that applies a scaling factor and a bias to the output value
|
||||
/// from the source function.
|
||||
///
|
||||
/// The function retrieves the output value from the source function, multiplies
|
||||
/// it with the scaling factor, adds the bias to it, then outputs the value.
|
||||
pub struct ScaleBias<'a, F: 'a> {
|
||||
/// Outputs a value.
|
||||
pub source: &'a F,
|
||||
|
||||
/// Scaling factor to apply to the output value from the source function.
|
||||
/// The default value is 1.0.
|
||||
pub scale: f64,
|
||||
|
||||
/// Bias to apply to the scaled output value from the source function.
|
||||
/// The default value is 0.0.
|
||||
pub bias: f64,
|
||||
}
|
||||
|
||||
impl<'a, F> ScaleBias<'a, F> {
|
||||
pub fn new(source: &'a F) -> Self {
|
||||
ScaleBias {
|
||||
source,
|
||||
scale: 1.0,
|
||||
bias: 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_scale(self, scale: f64) -> Self {
|
||||
ScaleBias { scale, ..self }
|
||||
}
|
||||
|
||||
pub fn set_bias(self, bias: f64) -> Self {
|
||||
ScaleBias { bias, ..self }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, F: NoiseFn<T> + 'a, T> NoiseFn<T> for ScaleBias<'a, F> {
|
||||
#[cfg(not(target_os = "emscripten"))]
|
||||
fn get(&self, point: T) -> f64 {
|
||||
(self.source.get(point)).mul_add(self.scale, self.bias)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "emscripten")]
|
||||
fn get(&self, point: T) -> f64 {
|
||||
(self.source.get(point) * self.scale) + self.bias
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ use super::seed_expan;
|
||||
use super::Sampler;
|
||||
use vek::*;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct RandomField {
|
||||
seed: u32,
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ fn calc_idx(v: Vec2<i32>) -> usize {
|
||||
(x ^ y) as usize
|
||||
}
|
||||
|
||||
// NOTE: Use 128 if TerrainChunkSize::RECT_SIZE.x = 128.
|
||||
const CACHE_LEN: usize = 32;
|
||||
|
||||
pub struct SmallCache<V: Default> {
|
||||
|
@ -1,4 +1,5 @@
|
||||
use super::{RandomField, Sampler};
|
||||
use rayon::prelude::*;
|
||||
use vek::*;
|
||||
|
||||
pub struct StructureGen2d {
|
||||
@ -9,6 +10,8 @@ pub struct StructureGen2d {
|
||||
seed_field: RandomField,
|
||||
}
|
||||
|
||||
pub type StructureField = (Vec2<i32>, u32);
|
||||
|
||||
impl StructureGen2d {
|
||||
pub fn new(seed: u32, freq: u32, spread: u32) -> Self {
|
||||
Self {
|
||||
@ -19,32 +22,130 @@ impl StructureGen2d {
|
||||
seed_field: RandomField::new(seed + 2),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn sample_to_index_internal(freq: i32, pos: Vec2<i32>) -> Vec2<i32> {
|
||||
pos.map(|e| e.div_euclid(freq))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn sample_to_index(&self, pos: Vec2<i32>) -> Vec2<i32> {
|
||||
Self::sample_to_index_internal(self.freq as i32, pos)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn freq_offset(freq: i32) -> i32 {
|
||||
freq / 2
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn spread_mul(spread: u32) -> u32 {
|
||||
spread * 2
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn index_to_sample_internal(
|
||||
freq: i32,
|
||||
freq_offset: i32,
|
||||
spread: i32,
|
||||
spread_mul: u32,
|
||||
x_field: RandomField,
|
||||
y_field: RandomField,
|
||||
seed_field: RandomField,
|
||||
index: Vec2<i32>,
|
||||
) -> StructureField {
|
||||
let center = index * freq + freq_offset;
|
||||
let pos = Vec3::from(center);
|
||||
(
|
||||
center
|
||||
+ Vec2::new(
|
||||
(x_field.get(pos) % spread_mul) as i32 - spread,
|
||||
(y_field.get(pos) % spread_mul) as i32 - spread,
|
||||
),
|
||||
seed_field.get(pos),
|
||||
)
|
||||
}
|
||||
|
||||
/// Note: Generates all possible closest samples for elements in the range of min to max,
|
||||
/// *exclusive.*
|
||||
pub fn par_iter(
|
||||
&self,
|
||||
min: Vec2<i32>,
|
||||
max: Vec2<i32>,
|
||||
) -> impl ParallelIterator<Item = StructureField> {
|
||||
let freq = self.freq;
|
||||
let spread = self.spread;
|
||||
let spread_mul = Self::spread_mul(spread);
|
||||
assert!(spread * 2 == spread_mul);
|
||||
assert!(spread_mul <= freq);
|
||||
let spread = spread as i32;
|
||||
let freq = freq as i32;
|
||||
let freq_offset = Self::freq_offset(freq);
|
||||
assert!(freq_offset * 2 == freq);
|
||||
|
||||
let min_index = Self::sample_to_index_internal(freq, min) - 1;
|
||||
let max_index = Self::sample_to_index_internal(freq, max) + 1;
|
||||
assert!(min_index.x < max_index.x);
|
||||
// NOTE: xlen > 0
|
||||
let xlen = (max_index.x - min_index.x) as u32;
|
||||
assert!(min_index.y < max_index.y);
|
||||
// NOTE: ylen > 0
|
||||
let ylen = (max_index.y - min_index.y) as u32;
|
||||
// NOTE: Cannot fail, since every product of u32s fits in a u64.
|
||||
let len = ylen as u64 * xlen as u64;
|
||||
// NOTE: since iteration is *exclusive* for the initial range, it's fine that we don't go
|
||||
// up to the maximum value.
|
||||
// NOTE: we convert to usize first, and then iterate, because we want to make sure we get
|
||||
// a properly indexed parallel iterator that can deal with the whole range at once.
|
||||
let x_field = self.x_field;
|
||||
let y_field = self.y_field;
|
||||
let seed_field = self.seed_field;
|
||||
(0..len).into_par_iter().map(move |xy| {
|
||||
let index = min_index + Vec2::new((xy % xlen as u64) as i32, (xy / xlen as u64) as i32);
|
||||
Self::index_to_sample_internal(
|
||||
freq,
|
||||
freq_offset,
|
||||
spread,
|
||||
spread_mul,
|
||||
x_field,
|
||||
y_field,
|
||||
seed_field,
|
||||
index,
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Sampler<'static> for StructureGen2d {
|
||||
type Index = Vec2<i32>;
|
||||
type Sample = [(Vec2<i32>, u32); 9];
|
||||
type Sample = [StructureField; 9];
|
||||
|
||||
fn get(&self, sample_pos: Self::Index) -> Self::Sample {
|
||||
let mut samples = [(Vec2::zero(), 0); 9];
|
||||
|
||||
let sample_closest = sample_pos.map(|e| e - e.rem_euclid(self.freq as i32));
|
||||
let freq = self.freq;
|
||||
let spread = self.spread;
|
||||
let spread_mul = Self::spread_mul(spread);
|
||||
let spread = spread as i32;
|
||||
let freq = freq as i32;
|
||||
let freq_offset = Self::freq_offset(freq);
|
||||
|
||||
let sample_closest = Self::sample_to_index_internal(freq, sample_pos);
|
||||
|
||||
for i in 0..3 {
|
||||
for j in 0..3 {
|
||||
let center = sample_closest
|
||||
+ Vec2::new(i, j).map(|e| e as i32 - 1) * self.freq as i32
|
||||
+ self.freq as i32 / 2;
|
||||
samples[i * 3 + j] = (
|
||||
center
|
||||
+ Vec2::new(
|
||||
(self.x_field.get(Vec3::from(center)) % (self.spread * 2)) as i32
|
||||
- self.spread as i32,
|
||||
(self.y_field.get(Vec3::from(center)) % (self.spread * 2)) as i32
|
||||
- self.spread as i32,
|
||||
),
|
||||
self.seed_field.get(Vec3::from(center)),
|
||||
let index = sample_closest + Vec2::new(i as i32, j as i32) - 1;
|
||||
let sample = Self::index_to_sample_internal(
|
||||
freq,
|
||||
freq_offset,
|
||||
spread,
|
||||
spread_mul,
|
||||
self.x_field,
|
||||
self.y_field,
|
||||
self.seed_field,
|
||||
index,
|
||||
);
|
||||
samples[i * 3 + j] = sample;
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user