Tweak rain gen; Fix seeding; Rename save->planet; Various other fixes
0c641573543d4d503e8c2d7e33b246709d7eface
This commit is contained in:
parent
39ecfb3cc2
commit
8df06365a7
16 changed files with 1309 additions and 894 deletions
188
Cargo.lock
generated
188
Cargo.lock
generated
|
@ -14,9 +14,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "ab_glyph_rasterizer"
|
||||
version = "0.1.6"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "363b9b88fad3af3be80bc8f762c9a3f9dfe906fd0327b8e92f1c12e5ae1b8bbb"
|
||||
checksum = "330223a1aecc308757b9926e9391c9b47f8ef2dbd8aea9df88312aea18c5e8d6"
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
|
@ -24,7 +24,7 @@ version = "0.7.6"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
|
||||
dependencies = [
|
||||
"getrandom 0.2.7",
|
||||
"getrandom",
|
||||
"once_cell",
|
||||
"version_check",
|
||||
]
|
||||
|
@ -76,9 +76,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.64"
|
||||
version = "1.0.65"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9a8f622bcf6ff3df478e9deba3e03e4e04b300f8e6a139e192c05fa3490afc7"
|
||||
checksum = "98161a4e3e2184da77bb14f02184cdd111e83bbbcc9979dfee3c44b9a85f5602"
|
||||
|
||||
[[package]]
|
||||
name = "approx"
|
||||
|
@ -697,7 +697,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "f6e9aa1866c1cf7ee000f281ce9e90d02d701f5c7380a107252017e58e2f5246"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"getrandom 0.2.7",
|
||||
"getrandom",
|
||||
"hashbrown",
|
||||
"instant",
|
||||
"tracing",
|
||||
|
@ -1210,17 +1210,6 @@ dependencies = [
|
|||
"byteorder",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.1.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"libc",
|
||||
"wasi 0.9.0+wasi-snapshot-preview1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.7"
|
||||
|
@ -1230,7 +1219,7 @@ dependencies = [
|
|||
"cfg-if 1.0.0",
|
||||
"js-sys",
|
||||
"libc",
|
||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||
"wasi",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
|
@ -1449,9 +1438,9 @@ checksum = "a257582fdcde896fd96463bf2d40eefea0580021c0712a0e2b028b60b47a837a"
|
|||
|
||||
[[package]]
|
||||
name = "inplace_it"
|
||||
version = "0.3.4"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67f0347836f3f6362c1e7efdadde2b1c4b4556d211310b70631bae7eb692070b"
|
||||
checksum = "e567468c50f3d4bc7397702e09b380139f9b9288b4e909b070571007f8b5bf78"
|
||||
|
||||
[[package]]
|
||||
name = "instant"
|
||||
|
@ -1479,9 +1468,9 @@ checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130"
|
|||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.59"
|
||||
version = "0.3.60"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "258451ab10b34f8af53416d1fdab72c22e805f0c92a1136d59470ec0b11138b2"
|
||||
checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47"
|
||||
dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
@ -1652,16 +1641,6 @@ version = "0.2.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e1bcdd74c20ad5d95aacd60ef9ba40fdf77f767051040541df557b7a9b2a2121"
|
||||
|
||||
[[package]]
|
||||
name = "noise"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "82051dd6745d5184c6efb7bc8be14892a7f6d4f3ad6dbf754d1c7d7d5fe24b43"
|
||||
dependencies = [
|
||||
"rand 0.7.3",
|
||||
"rand_xorshift",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-integer"
|
||||
version = "0.1.45"
|
||||
|
@ -1813,9 +1792,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.1.0"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
|
||||
checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e"
|
||||
|
||||
[[package]]
|
||||
name = "petgraph"
|
||||
|
@ -1839,6 +1818,16 @@ version = "0.3.25"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae"
|
||||
|
||||
[[package]]
|
||||
name = "planet"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bevy",
|
||||
"rand",
|
||||
"ron",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pp-rs"
|
||||
version = "0.2.1"
|
||||
|
@ -1895,19 +1884,6 @@ version = "0.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "17fd96390ed3feda12e1dfe2645ed587e0bea749e319333f104a33ff62f77a0b"
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
|
||||
dependencies = [
|
||||
"getrandom 0.1.16",
|
||||
"libc",
|
||||
"rand_chacha 0.2.2",
|
||||
"rand_core 0.5.1",
|
||||
"rand_hc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.5"
|
||||
|
@ -1915,18 +1891,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rand_chacha 0.3.1",
|
||||
"rand_core 0.6.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core 0.5.1",
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1936,43 +1902,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core 0.6.3",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.5.1"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
|
||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||
dependencies = [
|
||||
"getrandom 0.1.16",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
|
||||
dependencies = [
|
||||
"getrandom 0.2.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_hc"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
|
||||
dependencies = [
|
||||
"rand_core 0.5.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_xorshift"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77d416b86801d23dde1aa643023b775c3a462efc0ed96443add11546cdf1dca8"
|
||||
dependencies = [
|
||||
"rand_core 0.5.1",
|
||||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2060,17 +1999,6 @@ version = "1.0.11"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09"
|
||||
|
||||
[[package]]
|
||||
name = "save"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bevy",
|
||||
"noise",
|
||||
"rand 0.8.5",
|
||||
"ron",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.1.0"
|
||||
|
@ -2201,18 +2129,18 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.34"
|
||||
version = "1.0.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8c1b05ca9d106ba7d2e31a9dab4a64e7be2cce415321966ea3132c49a656e252"
|
||||
checksum = "c53f98874615aea268107765aa1ed8f6116782501d18e53d08b471733bea6c85"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.34"
|
||||
version = "1.0.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8f2591983642de85c921015f3f070c665a197ed69e417af436115e3a1407487"
|
||||
checksum = "f8b463991b4eab2d801e724172285ec4195c650e8ec79b149e6c2a8e6dd3f783"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -2324,21 +2252,21 @@ checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987"
|
|||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.3"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf"
|
||||
checksum = "dcc811dc4066ac62f84f11307873c4850cb653bfa9b1719cee2bd2204a4bc5dd"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.9"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
|
||||
checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.3"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04"
|
||||
checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
|
@ -2346,7 +2274,7 @@ version = "1.1.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd6469f4314d5f1ffec476e05f17cc9a78bc7a27a6a857842170bdf8d6f98d2f"
|
||||
dependencies = [
|
||||
"getrandom 0.2.7",
|
||||
"getrandom",
|
||||
"serde",
|
||||
]
|
||||
|
||||
|
@ -2368,12 +2296,6 @@ version = "1.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.9.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
|
@ -2382,9 +2304,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
|||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.82"
|
||||
version = "0.2.83"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc7652e3f6c4706c8d9cd54832c4a4ccb9b5336e2c3bd154d5cccfbf1c1f5f7d"
|
||||
checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"wasm-bindgen-macro",
|
||||
|
@ -2392,9 +2314,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-backend"
|
||||
version = "0.2.82"
|
||||
version = "0.2.83"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "662cd44805586bd52971b9586b1df85cdbbd9112e4ef4d8f41559c334dc6ac3f"
|
||||
checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"log",
|
||||
|
@ -2407,9 +2329,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-futures"
|
||||
version = "0.4.32"
|
||||
version = "0.4.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa76fb221a1f8acddf5b54ace85912606980ad661ac7a503b4570ffd3a624dad"
|
||||
checksum = "23639446165ca5a5de86ae1d8896b737ae80319560fbaa4c2887b7da6e7ebd7d"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"js-sys",
|
||||
|
@ -2419,9 +2341,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.82"
|
||||
version = "0.2.83"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b260f13d3012071dfb1512849c033b1925038373aea48ced3012c09df952c602"
|
||||
checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
|
@ -2429,9 +2351,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.82"
|
||||
version = "0.2.83"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5be8e654bdd9b79216c2929ab90721aa82faf65c48cdf08bdc4e7f51357b80da"
|
||||
checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -2442,15 +2364,15 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.82"
|
||||
version = "0.2.83"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6598dd0bd3c7d51095ff6531a5b23e02acdc81804e30d8f07afb77b7215a140a"
|
||||
checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f"
|
||||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.59"
|
||||
version = "0.3.60"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed055ab27f941423197eb86b2035720b1a3ce40504df082cac2ecc6ed73335a1"
|
||||
checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
|
@ -2657,7 +2579,7 @@ version = "0.1.0"
|
|||
dependencies = [
|
||||
"bevy",
|
||||
"bevy_pancam",
|
||||
"save",
|
||||
"planet",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
@ -5,13 +5,13 @@ edition = "2021"
|
|||
resolver = "2"
|
||||
|
||||
[features]
|
||||
debug = ["save/debug"]
|
||||
debug = ["planet/debug"]
|
||||
planet_view = ["render"]
|
||||
render = ["bevy/bevy_asset", "bevy/bevy_winit", "bevy/render", "save/render", "dep:bevy_pancam"]
|
||||
render = ["bevy/bevy_asset", "bevy/bevy_winit", "bevy/render", "planet/render", "dep:bevy_pancam"]
|
||||
default = ["render", "debug"]
|
||||
|
||||
[dependencies.save]
|
||||
path = "save"
|
||||
[dependencies.planet]
|
||||
path = "planet"
|
||||
default-features = false
|
||||
|
||||
[dependencies.bevy]
|
||||
|
|
File diff suppressed because one or more lines are too long
0
save/.gitignore → planet/.gitignore
vendored
0
save/.gitignore → planet/.gitignore
vendored
|
@ -1,5 +1,5 @@
|
|||
[package]
|
||||
name = "save"
|
||||
name = "planet"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
|
@ -8,10 +8,6 @@ debug = []
|
|||
render = ["bevy/render"]
|
||||
default = ["render", "debug"]
|
||||
|
||||
[dependencies.noise]
|
||||
version = "0.7.0"
|
||||
default-features = false
|
||||
|
||||
[dependencies.rand]
|
||||
version = "0.8.5"
|
||||
|
|
@ -38,3 +38,5 @@ pub mod world_manager;
|
|||
pub use world_manager::*;
|
||||
pub mod math_util;
|
||||
pub use math_util::*;
|
||||
pub mod perlin;
|
||||
pub mod saving;
|
|
@ -5,7 +5,7 @@ use std::{
|
|||
};
|
||||
|
||||
use bevy::math::Vec3A;
|
||||
use rand::Rng;
|
||||
use rand::{rngs::StdRng, Rng};
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum CartesianError {
|
||||
|
@ -34,11 +34,14 @@ impl Display for CartesianError {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn cartesian_coordinates(alpha: f32, beta: f32, radius: f32) -> Result<Vec3A, CartesianError> {
|
||||
pub fn cartesian_coordinates(
|
||||
alpha: f32,
|
||||
mut beta: f32,
|
||||
radius: f32,
|
||||
) -> Result<Vec3A, CartesianError> {
|
||||
if alpha < 0.0 || alpha > PI {
|
||||
return Err(CartesianError::InvalidAlpha(alpha));
|
||||
}
|
||||
let mut beta = beta.clone();
|
||||
|
||||
if beta < 0.0 {
|
||||
while beta < 0.0 {
|
||||
|
@ -57,17 +60,28 @@ pub fn cartesian_coordinates(alpha: f32, beta: f32, radius: f32) -> Result<Vec3A
|
|||
))
|
||||
}
|
||||
|
||||
pub fn random_point_in_sphere(radius: f32) -> Vec3A {
|
||||
let mut rng = rand::thread_rng();
|
||||
let x = rng.gen_range(-radius..radius);
|
||||
let y = rng.gen_range(-radius..radius);
|
||||
let z = rng.gen_range(-radius..radius);
|
||||
if x == 0.0 && y == 0.0 && z == 0.0 {
|
||||
return Vec3A::ZERO;
|
||||
}
|
||||
let mult = 1.0 / (x * x + y * y + z * z).sqrt();
|
||||
pub fn random_point_in_sphere(rng: &mut StdRng, radius: f32) -> Vec3A {
|
||||
// https://karthikkaranth.me/blog/generating-random-points-in-a-sphere/#better-choice-of-spherical-coordinates
|
||||
|
||||
Vec3A::new(mult * x, mult * y, mult * z)
|
||||
let u = rng.gen_range(0.0..1.0);
|
||||
let v = rng.gen_range(0.0..1.0);
|
||||
|
||||
let theta = u * TAU;
|
||||
let phi = f32::acos(2.0 * v - 1.0);
|
||||
|
||||
let r = f32::cbrt(rng.gen_range(0.0..radius));
|
||||
|
||||
let sin_theta = f32::sin(theta);
|
||||
let cos_theta = f32::cos(theta);
|
||||
|
||||
let sin_phi = f32::sin(phi);
|
||||
let cos_phi = f32::cos(phi);
|
||||
|
||||
Vec3A::new(
|
||||
r * sin_phi * cos_theta,
|
||||
r * sin_phi * sin_theta,
|
||||
r * cos_phi,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn mix_values(a: f32, b: f32, weight_b: f32) -> f32 {
|
95
planet/src/perlin.rs
Normal file
95
planet/src/perlin.rs
Normal file
|
@ -0,0 +1,95 @@
|
|||
const PERMUTATION: [u8; 256] = [
|
||||
151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69,
|
||||
142, 8, 99, 37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219,
|
||||
203, 117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175,
|
||||
74, 165, 71, 134, 139, 48, 27, 166, 77, 146, 158, 231, 83, 111, 229, 122, 60, 211, 133, 230,
|
||||
220, 105, 92, 41, 55, 46, 245, 40, 244, 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76,
|
||||
132, 187, 208, 89, 18, 169, 200, 196, 135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173,
|
||||
186, 3, 64, 52, 217, 226, 250, 124, 123, 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206,
|
||||
59, 227, 47, 16, 58, 17, 182, 189, 28, 42, 223, 183, 170, 213, 119, 248, 152, 2, 44, 154, 163,
|
||||
70, 221, 153, 101, 155, 167, 43, 172, 9, 129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224, 232,
|
||||
178, 185, 112, 104, 218, 246, 97, 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162,
|
||||
241, 81, 51, 145, 235, 249, 14, 239, 107, 49, 192, 214, 31, 181, 199, 106, 157, 184, 84, 204,
|
||||
176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, 93, 222, 114, 67, 29, 24, 72, 243, 141,
|
||||
128, 195, 78, 66, 215, 61, 156, 180,
|
||||
];
|
||||
|
||||
pub fn get_value(x: f32, y: f32, z: f32) -> f32 {
|
||||
let p = [PERMUTATION, PERMUTATION].concat();
|
||||
|
||||
let fx: i32 = f32::floor(x) as i32;
|
||||
let fy: i32 = f32::floor(y) as i32;
|
||||
let fz: i32 = f32::floor(z) as i32;
|
||||
|
||||
let xb: usize = (fx & 255) as usize;
|
||||
let yb: usize = (fy & 255) as usize;
|
||||
let zb: usize = (fz & 255) as usize;
|
||||
|
||||
let x = x - f32::floor(x);
|
||||
let y = y - f32::floor(y);
|
||||
let z = z - f32::floor(z);
|
||||
|
||||
let u = fade(x);
|
||||
let v = fade(y);
|
||||
let w = fade(z);
|
||||
|
||||
let a = p[xb] as usize + yb;
|
||||
let aa = p[a] as usize + zb;
|
||||
let ab = p[a + 1] as usize + zb;
|
||||
|
||||
let b = p[xb + 1] as usize + yb;
|
||||
let ba = p[b] as usize + zb;
|
||||
let bb = p[b + 1] as usize + zb;
|
||||
|
||||
scale(lerp(
|
||||
w,
|
||||
lerp(
|
||||
v,
|
||||
lerp(u, grad(p[aa], x, y, z), grad(p[ba], x - 1.0, y, z)),
|
||||
lerp(
|
||||
u,
|
||||
grad(p[ab], x, y - 1.0, z),
|
||||
grad(p[bb], x - 1.0, y - 1.0, z),
|
||||
),
|
||||
),
|
||||
lerp(
|
||||
v,
|
||||
lerp(
|
||||
u,
|
||||
grad(p[aa + 1], x, y, z - 1.0),
|
||||
grad(p[ba + 1], x - 1.0, y, z - 1.0),
|
||||
),
|
||||
lerp(
|
||||
u,
|
||||
grad(p[ab + 1], x, y - 1.0, z - 1.0),
|
||||
grad(p[bb + 1], x - 1.0, y - 1.0, z - 1.0),
|
||||
),
|
||||
),
|
||||
))
|
||||
}
|
||||
|
||||
fn fade(t: f32) -> f32 {
|
||||
t * t * t * (t * (t * 6.0 - 15.0) + 10.0)
|
||||
}
|
||||
|
||||
fn grad(hash: u8, x: f32, y: f32, z: f32) -> f32 {
|
||||
let h = hash & 15;
|
||||
let u = if h < 8 { x } else { y };
|
||||
let v = if h < 4 {
|
||||
y
|
||||
} else if (h == 12) || (h == 14) {
|
||||
x
|
||||
} else {
|
||||
z
|
||||
};
|
||||
|
||||
(if h & 1 == 0 { u } else { -u }) + (if h & 2 == 0 { v } else { -v })
|
||||
}
|
||||
|
||||
fn lerp(t: f32, a: f32, b: f32) -> f32 {
|
||||
a + t * (b - a)
|
||||
}
|
||||
|
||||
fn scale(n: f32) -> f32 {
|
||||
(1.0 + n) / 2.0
|
||||
}
|
255
planet/src/saving.rs
Normal file
255
planet/src/saving.rs
Normal file
|
@ -0,0 +1,255 @@
|
|||
use std::fmt::{self, Formatter};
|
||||
|
||||
use rand::{rngs::StdRng, SeedableRng};
|
||||
use serde::{
|
||||
de::{Error, MapAccess, SeqAccess, Visitor},
|
||||
Deserialize,
|
||||
};
|
||||
|
||||
use crate::{TerrainCell, World};
|
||||
|
||||
struct WorldTerrainAttributes {
|
||||
max_altitude: f32,
|
||||
min_altitude: f32,
|
||||
max_rainfall: f32,
|
||||
min_rainfall: f32,
|
||||
max_temperature: f32,
|
||||
min_temperature: f32,
|
||||
}
|
||||
impl Default for WorldTerrainAttributes {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
max_altitude: World::MIN_ALTITUDE,
|
||||
min_altitude: World::MAX_ALTITUDE,
|
||||
max_rainfall: World::MIN_RAINFALL,
|
||||
min_rainfall: World::MAX_RAINFALL,
|
||||
max_temperature: World::MIN_TEMPERATURE,
|
||||
min_temperature: World::MAX_TEMPERATURE,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for World {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
#[derive(Deserialize)]
|
||||
#[serde(field_identifier, rename_all = "snake_case")]
|
||||
enum Field {
|
||||
Width,
|
||||
Height,
|
||||
Seed,
|
||||
Terrain,
|
||||
ContinentOffsets,
|
||||
ContinentWidths,
|
||||
}
|
||||
|
||||
struct WorldVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for WorldVisitor {
|
||||
type Value = World;
|
||||
|
||||
fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
|
||||
formatter.write_str("struct World")
|
||||
}
|
||||
|
||||
fn visit_seq<V>(self, mut seq: V) -> Result<Self::Value, V::Error>
|
||||
where
|
||||
V: SeqAccess<'de>,
|
||||
{
|
||||
let width = seq
|
||||
.next_element()?
|
||||
.ok_or_else(|| Error::invalid_length(0, &self))?;
|
||||
|
||||
let height = seq
|
||||
.next_element()?
|
||||
.ok_or_else(|| Error::invalid_length(0, &self))?;
|
||||
|
||||
let seed = seq
|
||||
.next_element()?
|
||||
.ok_or_else(|| Error::invalid_length(0, &self))?;
|
||||
|
||||
let terrain: Vec<Vec<TerrainCell>> = seq
|
||||
.next_element()?
|
||||
.ok_or_else(|| Error::invalid_length(0, &self))?;
|
||||
|
||||
let continent_offsets = seq
|
||||
.next_element()?
|
||||
.ok_or_else(|| Error::invalid_length(0, &self))?;
|
||||
|
||||
let continent_widths = seq
|
||||
.next_element()?
|
||||
.ok_or_else(|| Error::invalid_length(0, &self))?;
|
||||
|
||||
let world_attributes = &mut WorldTerrainAttributes::default();
|
||||
let world_attributes =
|
||||
terrain
|
||||
.iter()
|
||||
.flatten()
|
||||
.fold(world_attributes, |attributes, cell| {
|
||||
if cell.altitude > attributes.max_altitude {
|
||||
attributes.max_altitude = cell.altitude;
|
||||
}
|
||||
if cell.altitude < attributes.min_altitude {
|
||||
attributes.min_altitude = cell.altitude;
|
||||
}
|
||||
|
||||
if cell.rainfall > attributes.max_rainfall {
|
||||
attributes.max_rainfall = cell.rainfall;
|
||||
}
|
||||
if cell.rainfall < attributes.min_rainfall {
|
||||
attributes.min_rainfall = cell.rainfall;
|
||||
}
|
||||
|
||||
if cell.temperature > attributes.max_temperature {
|
||||
attributes.max_temperature = cell.temperature;
|
||||
}
|
||||
if cell.temperature < attributes.min_temperature {
|
||||
attributes.min_temperature = cell.temperature;
|
||||
}
|
||||
attributes
|
||||
});
|
||||
|
||||
Ok(World {
|
||||
width,
|
||||
height,
|
||||
seed,
|
||||
terrain,
|
||||
continent_offsets,
|
||||
continent_widths,
|
||||
|
||||
max_altitude: world_attributes.max_altitude,
|
||||
min_altitude: world_attributes.min_altitude,
|
||||
max_rainfall: world_attributes.max_rainfall,
|
||||
min_rainfall: world_attributes.min_rainfall,
|
||||
max_temperature: world_attributes.max_temperature,
|
||||
min_temperature: world_attributes.min_temperature,
|
||||
|
||||
rng: StdRng::seed_from_u64(seed as u64),
|
||||
})
|
||||
}
|
||||
|
||||
fn visit_map<V>(self, mut map: V) -> Result<Self::Value, V::Error>
|
||||
where
|
||||
V: MapAccess<'de>,
|
||||
{
|
||||
let mut width = None;
|
||||
let mut height = None;
|
||||
let mut seed = None;
|
||||
let mut terrain = None;
|
||||
let mut continent_offsets = None;
|
||||
let mut continent_widths = None;
|
||||
|
||||
while let Some(key) = map.next_key()? {
|
||||
match key {
|
||||
Field::Width => {
|
||||
if width.is_some() {
|
||||
return Err(Error::duplicate_field("width"));
|
||||
}
|
||||
width = Some(map.next_value()?);
|
||||
}
|
||||
Field::Height => {
|
||||
if height.is_some() {
|
||||
return Err(Error::duplicate_field("height"));
|
||||
}
|
||||
height = Some(map.next_value()?);
|
||||
}
|
||||
Field::Seed => {
|
||||
if seed.is_some() {
|
||||
return Err(Error::duplicate_field("seed"));
|
||||
}
|
||||
seed = Some(map.next_value()?);
|
||||
}
|
||||
Field::Terrain => {
|
||||
if terrain.is_some() {
|
||||
return Err(Error::duplicate_field("terrain"));
|
||||
}
|
||||
terrain = Some(map.next_value()?);
|
||||
}
|
||||
Field::ContinentOffsets => {
|
||||
if continent_offsets.is_some() {
|
||||
return Err(Error::duplicate_field("continent_offsets"));
|
||||
}
|
||||
continent_offsets = Some(map.next_value()?);
|
||||
}
|
||||
Field::ContinentWidths => {
|
||||
if continent_widths.is_some() {
|
||||
return Err(Error::duplicate_field("continent_widths"));
|
||||
}
|
||||
continent_widths = Some(map.next_value()?);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let width = width.ok_or_else(|| Error::missing_field("width"))?;
|
||||
let height = height.ok_or_else(|| Error::missing_field("height"))?;
|
||||
let seed = seed.ok_or_else(|| Error::missing_field("seed"))?;
|
||||
let terrain: Vec<Vec<TerrainCell>> =
|
||||
terrain.ok_or_else(|| Error::missing_field("terrain"))?;
|
||||
let continent_offsets =
|
||||
continent_offsets.ok_or_else(|| Error::missing_field("continent_offsets"))?;
|
||||
let continent_widths =
|
||||
continent_widths.ok_or_else(|| Error::missing_field("continent_widths"))?;
|
||||
|
||||
let world_attributes = &mut WorldTerrainAttributes::default();
|
||||
let world_attributes =
|
||||
terrain
|
||||
.iter()
|
||||
.flatten()
|
||||
.fold(world_attributes, |attributes, cell| {
|
||||
if cell.altitude > attributes.max_altitude {
|
||||
attributes.max_altitude = cell.altitude;
|
||||
}
|
||||
if cell.altitude < attributes.min_altitude {
|
||||
attributes.min_altitude = cell.altitude;
|
||||
}
|
||||
|
||||
if cell.rainfall > attributes.max_rainfall {
|
||||
attributes.max_rainfall = cell.rainfall;
|
||||
}
|
||||
if cell.rainfall < attributes.min_rainfall {
|
||||
attributes.min_rainfall = cell.rainfall;
|
||||
}
|
||||
|
||||
if cell.temperature > attributes.max_temperature {
|
||||
attributes.max_temperature = cell.temperature;
|
||||
}
|
||||
if cell.temperature < attributes.min_temperature {
|
||||
attributes.min_temperature = cell.temperature;
|
||||
}
|
||||
attributes
|
||||
});
|
||||
|
||||
Ok(World {
|
||||
width,
|
||||
height,
|
||||
seed,
|
||||
terrain,
|
||||
continent_offsets,
|
||||
continent_widths,
|
||||
|
||||
max_altitude: world_attributes.max_altitude,
|
||||
min_altitude: world_attributes.min_altitude,
|
||||
max_rainfall: world_attributes.max_rainfall,
|
||||
min_rainfall: world_attributes.min_rainfall,
|
||||
max_temperature: world_attributes.max_temperature,
|
||||
min_temperature: world_attributes.min_temperature,
|
||||
|
||||
rng: StdRng::seed_from_u64(seed as u64),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const FIELDS: &'static [&'static str] = &[
|
||||
"width",
|
||||
"height",
|
||||
"seed",
|
||||
"terrain",
|
||||
"continent_offsets",
|
||||
"continent_widths",
|
||||
];
|
||||
|
||||
deserializer.deserialize_struct("World", FIELDS, WorldVisitor)
|
||||
}
|
||||
}
|
427
planet/src/world.rs
Normal file
427
planet/src/world.rs
Normal file
|
@ -0,0 +1,427 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
error::Error,
|
||||
f32::consts::{PI, TAU},
|
||||
fmt::{Debug, Display},
|
||||
};
|
||||
|
||||
// TODO: Logging doesn't seem to work here? Figure out why and fix
|
||||
|
||||
use crate::perlin;
|
||||
use bevy::{log::info, math::Vec3A, prelude::Vec2, utils::default};
|
||||
use rand::{rngs::StdRng, Rng, SeedableRng};
|
||||
|
||||
use crate::{cartesian_coordinates, mix_values, random_point_in_sphere, CartesianError, RepeatNum};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum WorldGenError {
|
||||
CartesianError(CartesianError),
|
||||
}
|
||||
impl Error for WorldGenError {
|
||||
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||
match *self {
|
||||
WorldGenError::CartesianError(ref e) => Some(e),
|
||||
}
|
||||
}
|
||||
|
||||
fn description(&self) -> &str {
|
||||
"description() is deprecated; use Display"
|
||||
}
|
||||
|
||||
fn cause(&self) -> Option<&dyn Error> {
|
||||
self.source()
|
||||
}
|
||||
}
|
||||
impl Display for WorldGenError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
WorldGenError::CartesianError(err) => Display::fmt(err, f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct World {
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
pub seed: u32,
|
||||
|
||||
pub terrain: Vec<Vec<TerrainCell>>,
|
||||
pub continent_offsets: [Vec2; World::NUM_CONTINENTS as usize],
|
||||
pub continent_widths: [f32; World::NUM_CONTINENTS as usize],
|
||||
#[serde(skip)]
|
||||
pub max_altitude: f32,
|
||||
#[serde(skip)]
|
||||
pub min_altitude: f32,
|
||||
#[serde(skip)]
|
||||
pub max_rainfall: f32,
|
||||
#[serde(skip)]
|
||||
pub min_rainfall: f32,
|
||||
#[serde(skip)]
|
||||
pub max_temperature: f32,
|
||||
#[serde(skip)]
|
||||
pub min_temperature: f32,
|
||||
#[serde(skip)]
|
||||
pub rng: StdRng,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
|
||||
pub struct Biome {
|
||||
pub altitude: f32,
|
||||
pub rainfall: f32,
|
||||
pub temperature: f32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
|
||||
pub struct TerrainCell {
|
||||
pub altitude: f32,
|
||||
pub rainfall: f32,
|
||||
pub temperature: f32,
|
||||
|
||||
#[serde(skip)]
|
||||
pub rain_accumulated: f32,
|
||||
#[serde(skip)]
|
||||
pub previous_rain_accumulated: f32,
|
||||
}
|
||||
|
||||
impl World {
|
||||
pub fn new(width: u32, height: u32, seed: u32) -> World {
|
||||
World {
|
||||
width,
|
||||
height,
|
||||
seed,
|
||||
terrain: vec![
|
||||
vec![TerrainCell::default(); width.try_into().unwrap()];
|
||||
height.try_into().unwrap()
|
||||
],
|
||||
continent_offsets: [default(); World::NUM_CONTINENTS as usize],
|
||||
continent_widths: [default(); World::NUM_CONTINENTS as usize],
|
||||
max_altitude: World::MIN_ALTITUDE,
|
||||
min_altitude: World::MAX_ALTITUDE,
|
||||
max_rainfall: World::MIN_RAINFALL,
|
||||
min_rainfall: World::MAX_RAINFALL,
|
||||
max_temperature: World::MIN_TEMPERATURE,
|
||||
min_temperature: World::MAX_TEMPERATURE,
|
||||
rng: StdRng::seed_from_u64(seed as u64),
|
||||
}
|
||||
}
|
||||
|
||||
pub const NUM_CONTINENTS: u8 = 7;
|
||||
pub const CONTINENT_FACTOR: f32 = 0.75;
|
||||
pub const CONTINENT_MIN_WIDTH_FACTOR: f32 = 3.0;
|
||||
pub const CONTINENT_MAX_WIDTH_FACTOR: f32 = 7.0;
|
||||
|
||||
pub const MIN_ALTITUDE: f32 = -10000.0;
|
||||
pub const MAX_ALTITUDE: f32 = 10000.0;
|
||||
pub const ALTITUDE_SPAN: f32 = World::MAX_ALTITUDE - World::MIN_ALTITUDE;
|
||||
|
||||
pub const MOUNTAIN_RANGE_MIX_FACTOR: f32 = 0.075;
|
||||
pub const MOUNTAIN_RANGE_WIDTH_FACTOR: f32 = 25.0;
|
||||
|
||||
pub const TERRAIN_NOISE_FACTOR_1: f32 = 0.2;
|
||||
pub const TERRAIN_NOISE_FACTOR_2: f32 = 0.15;
|
||||
pub const TERRAIN_NOISE_FACTOR_3: f32 = 0.1;
|
||||
|
||||
pub const MIN_RAINFALL: f32 = 0.0;
|
||||
pub const MAX_RAINFALL: f32 = 5000.0;
|
||||
pub const RAINFALL_SPAN: f32 = World::MAX_RAINFALL - World::MIN_RAINFALL;
|
||||
pub const RAINFALL_ALTITUDE_FACTOR: f32 = 1.0;
|
||||
pub const RAINFALL_DRYNESS_FACTOR: f32 = 0.001;
|
||||
pub const RAINFALL_DRYNESS_OFFSET: f32 = World::RAINFALL_DRYNESS_FACTOR * World::MAX_RAINFALL;
|
||||
|
||||
pub const MIN_TEMPERATURE: f32 = -50.0;
|
||||
pub const MAX_TEMPERATURE: f32 = 30.0;
|
||||
pub const TEMPERATURE_SPAN: f32 = World::MAX_TEMPERATURE - World::MIN_RAINFALL;
|
||||
pub const TEMPERATURE_ALTITUDE_FACTOR: f32 = 1.0;
|
||||
|
||||
pub fn generate(&mut self) -> Result<(), WorldGenError> {
|
||||
if let Err(err) = self.generate_altitude() {
|
||||
return Err(WorldGenError::CartesianError(err));
|
||||
}
|
||||
if let Err(err) = self.generate_rainfall() {
|
||||
return Err(WorldGenError::CartesianError(err));
|
||||
}
|
||||
if let Err(err) = self.generate_temperature() {
|
||||
return Err(WorldGenError::CartesianError(err));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn generate_continents(&mut self) {
|
||||
info!("Generating continents");
|
||||
let width = self.width as f32;
|
||||
let height = self.height as f32;
|
||||
|
||||
for i in 0..World::NUM_CONTINENTS {
|
||||
info!("{}/{}", i, World::NUM_CONTINENTS);
|
||||
|
||||
self.continent_offsets[i as usize].x = self
|
||||
.rng
|
||||
.gen_range(width * i as f32 * 2.0 / 5.0..(width * (i as f32 + 2.0) * 2.0 / 5.0))
|
||||
.repeat(width);
|
||||
self.continent_offsets[i as usize].y =
|
||||
self.rng.gen_range(height * 1.0 / 6.0..height * 5.0 / 6.0);
|
||||
|
||||
self.continent_widths[i as usize] = self
|
||||
.rng
|
||||
.gen_range(World::CONTINENT_MIN_WIDTH_FACTOR..World::CONTINENT_MAX_WIDTH_FACTOR);
|
||||
}
|
||||
info!("Done generating continents");
|
||||
}
|
||||
|
||||
fn continent_modifier(&self, x: usize, y: usize) -> f32 {
|
||||
let x = x as f32;
|
||||
let y = y as f32;
|
||||
let width = self.width as f32;
|
||||
let height = self.height as f32;
|
||||
|
||||
let mut max_value = 0.0;
|
||||
let beta_factor = f32::sin(PI * y / height);
|
||||
|
||||
for i in 0..World::NUM_CONTINENTS {
|
||||
let idx = i as usize;
|
||||
let Vec2 {
|
||||
x: continent_x,
|
||||
y: continent_y,
|
||||
} = self.continent_offsets[idx];
|
||||
|
||||
let distance_x = f32::min(
|
||||
f32::min(f32::abs(continent_x - x), f32::abs(width + continent_x - x)),
|
||||
f32::abs(continent_x - x - width),
|
||||
) * beta_factor;
|
||||
|
||||
let distance_y = f32::abs(continent_y - y);
|
||||
|
||||
let distance = f32::sqrt((distance_x * distance_x) + (distance_y * distance_y));
|
||||
|
||||
let value = f32::max(0.0, 1.0 - self.continent_widths[idx] * distance / width);
|
||||
|
||||
if value > max_value {
|
||||
max_value = value;
|
||||
}
|
||||
}
|
||||
|
||||
max_value
|
||||
}
|
||||
|
||||
fn generate_altitude(&mut self) -> Result<(), CartesianError> {
|
||||
info!("Generating altitude");
|
||||
self.generate_continents();
|
||||
|
||||
const RADIUS_1: f32 = 0.5;
|
||||
const RADIUS_2: f32 = 4.0;
|
||||
const RADIUS_3: f32 = 4.0;
|
||||
const RADIUS_4: f32 = 8.0;
|
||||
const RADIUS_5: f32 = 16.0;
|
||||
|
||||
let offset_1 = World::random_offset_vector(&mut self.rng);
|
||||
let offset_2 = World::random_offset_vector(&mut self.rng);
|
||||
let offset_3 = World::random_offset_vector(&mut self.rng);
|
||||
let offset_4 = World::random_offset_vector(&mut self.rng);
|
||||
let offset_5 = World::random_offset_vector(&mut self.rng);
|
||||
|
||||
for y in 0..self.terrain.len() {
|
||||
let alpha = (y as f32 / self.height as f32) * PI;
|
||||
|
||||
for x in 0..self.terrain[y].len() {
|
||||
let beta = (x as f32 / self.width as f32) * TAU;
|
||||
|
||||
let value_1 =
|
||||
self.random_noise_from_polar_coordinates(alpha, beta, RADIUS_1, offset_1)?;
|
||||
let value_2 =
|
||||
self.random_noise_from_polar_coordinates(alpha, beta, RADIUS_2, offset_2)?;
|
||||
let value_3 =
|
||||
self.random_noise_from_polar_coordinates(alpha, beta, RADIUS_3, offset_3)?;
|
||||
let value_4 =
|
||||
self.random_noise_from_polar_coordinates(alpha, beta, RADIUS_4, offset_4)?;
|
||||
let value_5 =
|
||||
self.random_noise_from_polar_coordinates(alpha, beta, RADIUS_5, offset_5)?;
|
||||
|
||||
let value_a = mix_values(
|
||||
self.continent_modifier(x, y),
|
||||
value_3,
|
||||
World::TERRAIN_NOISE_FACTOR_1,
|
||||
) * mix_values(1.0, value_4, World::TERRAIN_NOISE_FACTOR_2)
|
||||
* mix_values(1.0, value_5, World::TERRAIN_NOISE_FACTOR_3);
|
||||
|
||||
let value_b = value_a * 0.04 + 0.48;
|
||||
|
||||
let value_c = mix_values(
|
||||
self.mountain_range_noise_from_random_noise(mix_values(
|
||||
value_1,
|
||||
value_2,
|
||||
World::MOUNTAIN_RANGE_MIX_FACTOR,
|
||||
)),
|
||||
value_3,
|
||||
World::TERRAIN_NOISE_FACTOR_1 * 1.5,
|
||||
) * mix_values(1.0, value_4, World::TERRAIN_NOISE_FACTOR_2 * 1.5)
|
||||
* mix_values(1.0, value_5, World::TERRAIN_NOISE_FACTOR_3 * 1.5);
|
||||
|
||||
let mut value_d = mix_values(value_a, value_c, 0.25);
|
||||
value_d = mix_values(value_d, value_c, 0.1);
|
||||
value_d = mix_values(value_d, value_b, 0.1);
|
||||
|
||||
let altitude = World::calculate_altitude(value_d);
|
||||
self.terrain[y][x].altitude = altitude;
|
||||
|
||||
if altitude > self.max_altitude {
|
||||
self.max_altitude = altitude;
|
||||
}
|
||||
if altitude < self.min_altitude {
|
||||
self.min_altitude = altitude;
|
||||
}
|
||||
}
|
||||
}
|
||||
info!("Done generating altitude");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn random_offset_vector(rng: &mut StdRng) -> Vec3A {
|
||||
random_point_in_sphere(rng, 1000.0)
|
||||
}
|
||||
|
||||
fn random_noise_from_polar_coordinates(
|
||||
&self,
|
||||
alpha: f32,
|
||||
beta: f32,
|
||||
radius: f32,
|
||||
offset: Vec3A,
|
||||
) -> Result<f32, CartesianError> {
|
||||
let cartesian = cartesian_coordinates(alpha, beta, radius)?;
|
||||
Ok(perlin::get_value(
|
||||
cartesian.x + offset.x,
|
||||
cartesian.y + offset.y,
|
||||
cartesian.z + offset.z,
|
||||
))
|
||||
}
|
||||
|
||||
fn mountain_range_noise_from_random_noise(&self, noise: f32) -> f32 {
|
||||
let noise = noise * 2.0 - 1.0;
|
||||
|
||||
let value_1 = -f32::exp(-f32::powi(
|
||||
noise * World::MOUNTAIN_RANGE_WIDTH_FACTOR + 1.0,
|
||||
2,
|
||||
));
|
||||
let value_2 = f32::exp(-f32::powi(
|
||||
noise * World::MOUNTAIN_RANGE_WIDTH_FACTOR - 1.0,
|
||||
2,
|
||||
));
|
||||
let value_3 = -f32::exp(-f32::powi(
|
||||
noise * World::MOUNTAIN_RANGE_WIDTH_FACTOR + World::MOUNTAIN_RANGE_WIDTH_FACTOR / 2.0,
|
||||
2,
|
||||
));
|
||||
let value_4 = f32::exp(-f32::powi(
|
||||
noise * World::MOUNTAIN_RANGE_WIDTH_FACTOR - World::MOUNTAIN_RANGE_WIDTH_FACTOR / 2.0,
|
||||
2,
|
||||
));
|
||||
|
||||
(value_1 + value_2 + value_3 + value_4 + 1.0) / 2.0
|
||||
}
|
||||
|
||||
fn calculate_altitude(raw_altitude: f32) -> f32 {
|
||||
World::MIN_ALTITUDE + (raw_altitude * World::ALTITUDE_SPAN)
|
||||
}
|
||||
|
||||
fn generate_rainfall(&mut self) -> Result<(), CartesianError> {
|
||||
info!("Generating rainfall");
|
||||
// let offset = World::random_offset_vector(&mut self.rng);
|
||||
|
||||
let height = self.terrain.len();
|
||||
for y in 0..height {
|
||||
info!("Rainfall: {}/{}", y, height);
|
||||
let alpha = (y as f32 / self.height as f32) * PI;
|
||||
let latitude_modifier = f32::sin(2.0 * alpha);
|
||||
|
||||
let width = self.terrain[y].len();
|
||||
for x in 0..width {
|
||||
// let beta = (x as f32 / self.width as f32) * TAU;
|
||||
|
||||
let diff_cell_x =
|
||||
(width + x + f32::floor(latitude_modifier * width as f32 / 50.0) as usize)
|
||||
% width;
|
||||
let diff_cell = &self.terrain[y][diff_cell_x];
|
||||
let diff_altitude = f32::max(0.0, diff_cell.altitude);
|
||||
|
||||
let mut cell = &mut self.terrain[y][x];
|
||||
let altitude = f32::max(0.0, cell.altitude);
|
||||
|
||||
let altitude_factor =
|
||||
f32::max(0.0, 2.0 * (altitude - diff_altitude) / World::MAX_ALTITUDE);
|
||||
|
||||
let rainfall = f32::min(
|
||||
World::MAX_RAINFALL,
|
||||
World::calculate_rainfall(altitude_factor),
|
||||
);
|
||||
|
||||
cell.rainfall = rainfall;
|
||||
|
||||
if rainfall > self.max_rainfall {
|
||||
self.max_rainfall = rainfall;
|
||||
}
|
||||
if rainfall < self.min_rainfall {
|
||||
self.min_rainfall = rainfall;
|
||||
}
|
||||
}
|
||||
}
|
||||
info!("Done generating rainfall");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn calculate_rainfall(raw_rainfall: f32) -> f32 {
|
||||
f32::clamp(
|
||||
(raw_rainfall * (World::RAINFALL_SPAN + World::RAINFALL_DRYNESS_OFFSET))
|
||||
+ World::MIN_RAINFALL
|
||||
- World::RAINFALL_DRYNESS_OFFSET,
|
||||
0.0,
|
||||
World::MAX_RAINFALL,
|
||||
)
|
||||
}
|
||||
|
||||
fn generate_temperature(&mut self) -> Result<(), CartesianError> {
|
||||
let offset = World::random_offset_vector(&mut self.rng);
|
||||
const RADIUS: f32 = 2.0;
|
||||
|
||||
for y in 0..self.terrain.len() {
|
||||
let alpha = (y as f32 / self.height as f32) * PI;
|
||||
for x in 0..self.terrain[y].len() {
|
||||
let beta = (x as f32 / self.width as f32) * TAU;
|
||||
|
||||
let random_noise =
|
||||
self.random_noise_from_polar_coordinates(alpha, beta, RADIUS, offset)?;
|
||||
|
||||
let cell = &mut self.terrain[y][x];
|
||||
|
||||
let altitude_factor = f32::max(
|
||||
0.0,
|
||||
(cell.altitude / World::MAX_ALTITUDE)
|
||||
* 2.5
|
||||
* World::TEMPERATURE_ALTITUDE_FACTOR,
|
||||
);
|
||||
|
||||
let latitude_modifer = (alpha * 0.8) + (random_noise * 0.2 * PI);
|
||||
let base_temperature = World::calculate_temperature(f32::sin(latitude_modifer));
|
||||
|
||||
let temperature = base_temperature * (1.0 - altitude_factor);
|
||||
cell.temperature = temperature;
|
||||
|
||||
if temperature > self.max_temperature {
|
||||
self.max_temperature = temperature;
|
||||
}
|
||||
if temperature < self.min_temperature {
|
||||
self.min_temperature = temperature;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn calculate_temperature(raw_temperature: f32) -> f32 {
|
||||
f32::clamp(
|
||||
(raw_temperature * World::TEMPERATURE_SPAN) + World::MIN_TEMPERATURE,
|
||||
World::MIN_TEMPERATURE,
|
||||
World::MAX_TEMPERATURE,
|
||||
)
|
||||
}
|
||||
}
|
379
planet/src/world_manager.rs
Normal file
379
planet/src/world_manager.rs
Normal file
|
@ -0,0 +1,379 @@
|
|||
#[cfg(feature = "render")]
|
||||
use crate::TerrainCell;
|
||||
use crate::{World, WorldGenError};
|
||||
#[cfg(all(feature = "debug", feature = "render"))]
|
||||
use bevy::log::debug;
|
||||
use bevy::log::warn;
|
||||
#[cfg(feature = "debug")]
|
||||
use bevy::utils::default;
|
||||
#[cfg(feature = "render")]
|
||||
use bevy::{
|
||||
asset::{Assets, HandleId},
|
||||
render::render_resource::Extent3d,
|
||||
render::{color::Color, texture::Image},
|
||||
};
|
||||
use rand::random;
|
||||
use std::{
|
||||
error::Error,
|
||||
fmt::Display,
|
||||
fs::File,
|
||||
io::{self, Read, Write},
|
||||
path::Path,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum LoadError {
|
||||
MissingSave(io::Error),
|
||||
InvalidSave(ron::Error),
|
||||
}
|
||||
impl Error for LoadError {
|
||||
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||
match self {
|
||||
LoadError::MissingSave(error) => Some(error),
|
||||
LoadError::InvalidSave(error) => Some(error),
|
||||
}
|
||||
}
|
||||
|
||||
fn description(&self) -> &str {
|
||||
"description() is deprecated; use Display"
|
||||
}
|
||||
|
||||
fn cause(&self) -> Option<&dyn Error> {
|
||||
self.source()
|
||||
}
|
||||
}
|
||||
impl Display for LoadError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
LoadError::MissingSave(_) => f.write_str("No save found at given path"),
|
||||
LoadError::InvalidSave(_) => f.write_str("Loaded file is not a valid save"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum SaveError {
|
||||
MissingWorld,
|
||||
SerializationError(ron::Error),
|
||||
FailedToWrite(io::Error),
|
||||
}
|
||||
impl Error for SaveError {
|
||||
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||
match self {
|
||||
SaveError::MissingWorld => None,
|
||||
SaveError::SerializationError(error) => Some(error),
|
||||
SaveError::FailedToWrite(err) => Some(err),
|
||||
}
|
||||
}
|
||||
|
||||
fn description(&self) -> &str {
|
||||
"description() is deprecated; use Display"
|
||||
}
|
||||
|
||||
fn cause(&self) -> Option<&dyn Error> {
|
||||
self.source()
|
||||
}
|
||||
}
|
||||
impl Display for SaveError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
SaveError::MissingWorld => f.write_str("No world to save found."),
|
||||
SaveError::SerializationError(_) => f.write_str("Failed to serialize world."),
|
||||
SaveError::FailedToWrite(_) => f.write_str("Failed to write save file."),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct WorldManager {
|
||||
#[cfg(feature = "render")]
|
||||
pub image_handle_id: Option<HandleId>,
|
||||
world: Option<World>,
|
||||
#[cfg(feature = "render")]
|
||||
rainfall_visible: bool,
|
||||
#[cfg(feature = "render")]
|
||||
temperature_visible: bool,
|
||||
#[cfg(feature = "render")]
|
||||
contours: bool,
|
||||
}
|
||||
|
||||
impl WorldManager {
|
||||
pub fn new() -> WorldManager {
|
||||
Self {
|
||||
#[cfg(feature = "render")]
|
||||
image_handle_id: None,
|
||||
world: None,
|
||||
#[cfg(feature = "render")]
|
||||
rainfall_visible: false,
|
||||
#[cfg(feature = "render")]
|
||||
temperature_visible: false,
|
||||
#[cfg(feature = "render")]
|
||||
contours: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn save_world<P: AsRef<Path>>(&self, path: P) -> Result<(), SaveError> {
|
||||
let world = match self.get_world() {
|
||||
Some(world) => world,
|
||||
None => {
|
||||
warn!("No world to save");
|
||||
return Err(SaveError::MissingWorld);
|
||||
}
|
||||
};
|
||||
#[cfg(feature = "debug")]
|
||||
let serialized = match ron::ser::to_string_pretty(world, default()) {
|
||||
Ok(serialized) => serialized,
|
||||
Err(err) => {
|
||||
return Err(SaveError::SerializationError(err));
|
||||
}
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "debug"))]
|
||||
let serialized = match ron::to_string(world) {
|
||||
Ok(serialized) => serialized,
|
||||
Err(err) => {
|
||||
return Err(SaveError::SerializationError(err));
|
||||
}
|
||||
};
|
||||
|
||||
match File::create(path).unwrap().write_all(serialized.as_bytes()) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) => Err(SaveError::FailedToWrite(err)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_world<P: AsRef<Path>>(
|
||||
&mut self,
|
||||
path: P,
|
||||
#[cfg(feature = "render")] images: &mut Assets<Image>,
|
||||
) -> Result<(), LoadError> {
|
||||
let mut file = match File::open(path) {
|
||||
Ok(file) => file,
|
||||
Err(err) => {
|
||||
return Err(LoadError::MissingSave(err));
|
||||
}
|
||||
};
|
||||
let mut buf = String::new();
|
||||
match file.read_to_string(&mut buf) {
|
||||
Ok(_) => {}
|
||||
Err(err) => {
|
||||
return Err(LoadError::MissingSave(err));
|
||||
}
|
||||
};
|
||||
match ron::from_str(buf.as_str()) {
|
||||
Ok(world) => {
|
||||
#[cfg(feature = "render")]
|
||||
let World { height, width, .. } = world;
|
||||
self.world = Some(world);
|
||||
#[cfg(feature = "render")]
|
||||
{
|
||||
let image_handle = &images.get_handle(
|
||||
self.image_handle_id
|
||||
.expect("Missing image handle, even though world is present"),
|
||||
);
|
||||
images
|
||||
.get_mut(image_handle)
|
||||
.expect("Handle for missing image")
|
||||
.resize(Extent3d {
|
||||
width,
|
||||
height,
|
||||
depth_or_array_layers: 0,
|
||||
});
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Err(err) => Err(LoadError::InvalidSave(err)),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "render")]
|
||||
pub fn toggle_rainfall(&mut self) {
|
||||
#[cfg(feature = "debug")]
|
||||
if self.rainfall_visible {
|
||||
debug!("Turning rainfall off");
|
||||
} else {
|
||||
debug!("Turning rainfall on");
|
||||
}
|
||||
self.rainfall_visible = !self.rainfall_visible;
|
||||
}
|
||||
|
||||
#[cfg(feature = "render")]
|
||||
pub fn toggle_temperature(&mut self) {
|
||||
#[cfg(feature = "debug")]
|
||||
if self.temperature_visible {
|
||||
debug!("Turning temperature off");
|
||||
} else {
|
||||
debug!("Turning temperature on");
|
||||
}
|
||||
self.temperature_visible = !self.temperature_visible;
|
||||
}
|
||||
|
||||
#[cfg(feature = "render")]
|
||||
pub fn toggle_contours(&mut self) {
|
||||
#[cfg(feature = "debug")]
|
||||
if self.contours {
|
||||
debug!("Turning terrain contours off");
|
||||
} else {
|
||||
debug!("Turning terrain contours on");
|
||||
}
|
||||
self.contours = !self.contours;
|
||||
}
|
||||
|
||||
pub fn get_world(&self) -> Option<&World> {
|
||||
self.world.as_ref()
|
||||
}
|
||||
pub fn world(&self) -> &World {
|
||||
assert!(self.world.is_some(), "No world.");
|
||||
self.get_world().unwrap()
|
||||
}
|
||||
|
||||
pub fn new_world(&mut self) -> Result<&World, WorldGenError> {
|
||||
let seed = random();
|
||||
let mut new_world = World::new(400, 200, seed);
|
||||
new_world.generate()?;
|
||||
self.world = Some(new_world);
|
||||
Ok(self.get_world().unwrap())
|
||||
}
|
||||
|
||||
#[cfg(feature = "render")]
|
||||
fn generate_color(&self, cell: &TerrainCell) -> Color {
|
||||
let altitude_color = if self.contours {
|
||||
WorldManager::altitude_contour_color(cell.altitude)
|
||||
} else {
|
||||
WorldManager::altitude_color(cell.altitude)
|
||||
};
|
||||
|
||||
let mut layer_count = 1.0;
|
||||
|
||||
let mut r = altitude_color.r();
|
||||
let mut g = altitude_color.g();
|
||||
let mut b = altitude_color.b();
|
||||
|
||||
if self.rainfall_visible {
|
||||
layer_count += 1.0;
|
||||
let rainfall_color = self.rainfall_contour_color(cell.rainfall);
|
||||
// if self.contours {
|
||||
// self.rainfall_contour_color(cell.rainfall)
|
||||
// } else {
|
||||
// WorldManager::rainfall_color(cell.rainfall)
|
||||
// };
|
||||
|
||||
r += rainfall_color.r();
|
||||
g += rainfall_color.g();
|
||||
b += rainfall_color.b();
|
||||
}
|
||||
|
||||
if self.temperature_visible {
|
||||
layer_count += 1.0;
|
||||
let temperature_color = self.temperature_contour_color(cell.temperature);
|
||||
// if self.contours {
|
||||
// self.temperature_contour_color(cell.temperature)
|
||||
// } else {
|
||||
// WorldManager::temperature_color(cell.temperature)
|
||||
// };
|
||||
|
||||
r += temperature_color.r();
|
||||
g += temperature_color.g();
|
||||
b += temperature_color.b();
|
||||
}
|
||||
|
||||
Color::rgb(r / layer_count, g / layer_count, b / layer_count)
|
||||
}
|
||||
|
||||
#[cfg(feature = "render")]
|
||||
fn altitude_color(altitude: f32) -> Color {
|
||||
if altitude < 0.0 {
|
||||
Color::rgb(0.0, 0.0, (2.0 - altitude / World::MIN_ALTITUDE) / 2.0)
|
||||
} else {
|
||||
let mult = (1.0 + altitude / World::MAX_ALTITUDE) / 2.0;
|
||||
|
||||
Color::rgb(0.58 * mult, 0.29 * mult, 0.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "render")]
|
||||
fn altitude_contour_color(altitude: f32) -> Color {
|
||||
if altitude < 0.0 {
|
||||
Color::rgb(0.0, 0.0, (2.0 - altitude / World::MIN_ALTITUDE) / 2.0)
|
||||
} else {
|
||||
let mut shade_value = 1.0;
|
||||
|
||||
while shade_value > altitude / World::MAX_ALTITUDE {
|
||||
shade_value -= 0.05;
|
||||
}
|
||||
|
||||
Color::rgb(shade_value, shade_value, shade_value)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "render")]
|
||||
fn rainfall_contour_color(&self, rainfall: f32) -> Color {
|
||||
Color::rgb(
|
||||
0.0,
|
||||
f32::floor((rainfall / self.world().max_rainfall) / 0.1),
|
||||
0.0,
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(feature = "render")]
|
||||
fn rainfall_color(rainfall: f32) -> Color {
|
||||
if rainfall <= 0.0 {
|
||||
Color::BLACK
|
||||
} else {
|
||||
Color::rgb(0.0, rainfall / World::MAX_RAINFALL, 0.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "render")]
|
||||
fn temperature_contour_color(&self, temperature: f32) -> Color {
|
||||
let mut shade_value = 1.0;
|
||||
let value = (temperature - self.world().min_temperature)
|
||||
/ (self.world().max_temperature - self.world().min_temperature);
|
||||
|
||||
while shade_value > value {
|
||||
shade_value -= 0.1;
|
||||
}
|
||||
|
||||
Color::rgb(shade_value, 0.0, 1.0 - shade_value)
|
||||
}
|
||||
|
||||
#[cfg(feature = "render")]
|
||||
fn temperature_color(temperature: f32) -> Color {
|
||||
let normalized_temperature = WorldManager::normalize_temperature(temperature);
|
||||
Color::rgb(normalized_temperature, 1.0 - normalized_temperature, 0.0)
|
||||
}
|
||||
|
||||
#[cfg(feature = "render")]
|
||||
fn normalize_temperature(temperature: f32) -> f32 {
|
||||
(temperature - World::MIN_TEMPERATURE) / World::TEMPERATURE_SPAN
|
||||
}
|
||||
|
||||
#[cfg(feature = "render")]
|
||||
pub fn world_colors(&self) -> Vec<Color> {
|
||||
match self.get_world() {
|
||||
None => panic!("Called world_colors before generating world"),
|
||||
Some(world) => {
|
||||
let terrain_cells: Vec<_> = world.terrain.iter().rev().flatten().collect();
|
||||
|
||||
terrain_cells
|
||||
.iter()
|
||||
.map(|cell| self.generate_color(cell))
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "render")]
|
||||
pub fn world_color_bytes(&self) -> Vec<u8> {
|
||||
self.world_colors()
|
||||
.iter()
|
||||
.flat_map(|color| {
|
||||
color
|
||||
.as_rgba_f32()
|
||||
.iter()
|
||||
.flat_map(|num| num.to_le_bytes())
|
||||
.collect::<Vec<u8>>()
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
|
@ -1,445 +0,0 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
error::Error,
|
||||
f32::consts::{PI, TAU},
|
||||
fmt::{Debug, Display},
|
||||
};
|
||||
|
||||
// TODO: Logging doesn't seem to work here? Figure out why and fix
|
||||
|
||||
use bevy::{log::info, math::Vec3A, prelude::Vec2, utils::default};
|
||||
use noise::{NoiseFn, Perlin, Seedable};
|
||||
use rand::Rng;
|
||||
|
||||
use crate::{cartesian_coordinates, mix_values, random_point_in_sphere, CartesianError, RepeatNum};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum WorldGenError {
|
||||
CartesianError(CartesianError),
|
||||
}
|
||||
impl Error for WorldGenError {
|
||||
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||
match *self {
|
||||
WorldGenError::CartesianError(ref e) => Some(e),
|
||||
}
|
||||
}
|
||||
|
||||
fn description(&self) -> &str {
|
||||
"description() is deprecated; use Display"
|
||||
}
|
||||
|
||||
fn cause(&self) -> Option<&dyn Error> {
|
||||
self.source()
|
||||
}
|
||||
}
|
||||
impl Display for WorldGenError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
WorldGenError::CartesianError(err) => Display::fmt(err, f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize, Serialize)]
|
||||
pub struct World {
|
||||
pub width: i32,
|
||||
pub height: i32,
|
||||
pub seed: u32,
|
||||
|
||||
pub terrain: Vec<Vec<TerrainCell>>,
|
||||
continent_offsets: [Vec2; World::NUM_CONTINENTS as usize],
|
||||
continent_widths: [f32; World::NUM_CONTINENTS as usize],
|
||||
#[serde(skip)]
|
||||
perlin: Perlin,
|
||||
}
|
||||
impl Debug for World {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("World")
|
||||
.field("width", &self.width)
|
||||
.field("height", &self.height)
|
||||
.field("seed", &self.seed)
|
||||
.field(
|
||||
"Average Rainfall",
|
||||
&(self
|
||||
.terrain
|
||||
.iter()
|
||||
.flatten()
|
||||
.map(|cell| cell.rainfall)
|
||||
.sum::<f32>()
|
||||
/ (self.width * self.height) as f32),
|
||||
)
|
||||
.field("continent_offsets", &self.continent_offsets)
|
||||
.field("continent_widths", &self.continent_widths)
|
||||
.field("perlin", &self.perlin)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
|
||||
pub(crate) struct Biome {
|
||||
pub altitude: f32,
|
||||
pub rainfall: f32,
|
||||
pub temperature: f32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
|
||||
pub struct TerrainCell {
|
||||
pub altitude: f32,
|
||||
pub rainfall: f32,
|
||||
pub temperature: f32,
|
||||
|
||||
#[serde(skip)]
|
||||
pub rain_accumulated: f32,
|
||||
#[serde(skip)]
|
||||
pub previous_rain_accumulated: f32,
|
||||
}
|
||||
|
||||
impl World {
|
||||
pub fn new(width: i32, height: i32, seed: u32) -> World {
|
||||
World {
|
||||
width,
|
||||
height,
|
||||
seed,
|
||||
terrain: vec![
|
||||
vec![TerrainCell::default(); width.try_into().unwrap()];
|
||||
height.try_into().unwrap()
|
||||
],
|
||||
continent_offsets: [default(); Self::NUM_CONTINENTS as usize],
|
||||
continent_widths: [default(); Self::NUM_CONTINENTS as usize],
|
||||
perlin: Perlin::new().set_seed(seed),
|
||||
}
|
||||
}
|
||||
|
||||
pub const NUM_CONTINENTS: u8 = 7;
|
||||
pub const CONTINENT_FACTOR: f32 = 0.7;
|
||||
pub const CONTINENT_MIN_WIDTH_FACTOR: f32 = 3.0;
|
||||
pub const CONTINENT_MAX_WIDTH_FACTOR: f32 = 7.0;
|
||||
|
||||
pub const MIN_ALTITUDE: f32 = -10000.0;
|
||||
pub const MAX_ALTITUDE: f32 = 10000.0;
|
||||
pub const ALTITUDE_SPAN: f32 = Self::MAX_ALTITUDE - Self::MIN_ALTITUDE;
|
||||
|
||||
pub const MOUNTAIN_RANGE_MIX_FACTOR: f32 = 0.075;
|
||||
pub const MOUNTAIN_RANGE_WIDTH_FACTOR: f32 = 25.0;
|
||||
|
||||
pub const TERRAIN_NOISE_FACTOR_1: f32 = 0.2;
|
||||
pub const TERRAIN_NOISE_FACTOR_2: f32 = 0.15;
|
||||
pub const TERRAIN_NOISE_FACTOR_3: f32 = 0.1;
|
||||
|
||||
pub const MIN_RAINFALL: f32 = -20.0;
|
||||
pub const MAX_RAINFALL: f32 = 100.0;
|
||||
pub const RAINFALL_SPAN: f32 = Self::MAX_RAINFALL - Self::MIN_RAINFALL;
|
||||
pub const RAINFALL_ALTITUDE_FACTOR: f32 = 1.0;
|
||||
|
||||
pub const MIN_TEMPERATURE: f32 = -50.0;
|
||||
pub const MAX_TEMPERATURE: f32 = 30.0;
|
||||
pub const TEMPERATURE_SPAN: f32 = Self::MAX_TEMPERATURE - Self::MIN_RAINFALL;
|
||||
pub const TEMPERATURE_ALTITUDE_FACTOR: f32 = 1.0;
|
||||
|
||||
pub fn generate(&mut self) -> Result<(), WorldGenError> {
|
||||
if let Err(err) = self.generate_altitude() {
|
||||
return Err(WorldGenError::CartesianError(err));
|
||||
}
|
||||
if let Err(err) = self.generate_rainfall() {
|
||||
return Err(WorldGenError::CartesianError(err));
|
||||
}
|
||||
if let Err(err) = self.generate_temperature() {
|
||||
return Err(WorldGenError::CartesianError(err));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn generate_continents(&mut self) {
|
||||
info!("Generating continents");
|
||||
let mut rng = rand::thread_rng();
|
||||
let width = self.width as f32;
|
||||
let height = self.height as f32;
|
||||
|
||||
for i in 0..Self::NUM_CONTINENTS {
|
||||
info!("{}/{}", i, Self::NUM_CONTINENTS);
|
||||
|
||||
self.continent_offsets[i as usize].x = rng
|
||||
.gen_range(width * i as f32 * 2.0 / 5.0..width * (i as f32 + 2.0) * 2.0 / 5.0)
|
||||
.repeat(width);
|
||||
self.continent_offsets[i as usize].y =
|
||||
rng.gen_range(height * 1.0 / 6.0..height * 5.0 / 6.0);
|
||||
|
||||
self.continent_widths[i as usize] =
|
||||
rng.gen_range(Self::CONTINENT_MIN_WIDTH_FACTOR..Self::CONTINENT_MAX_WIDTH_FACTOR);
|
||||
}
|
||||
info!("Done generating continents");
|
||||
}
|
||||
|
||||
fn continent_modifier(&self, x: usize, y: usize) -> f32 {
|
||||
let x = x as f32;
|
||||
let y = y as f32;
|
||||
let width = self.width as f32;
|
||||
let height = self.height as f32;
|
||||
|
||||
let mut max_value = 0.0;
|
||||
let beta_factor = f32::sin(PI * y / height);
|
||||
|
||||
for i in 0..Self::NUM_CONTINENTS {
|
||||
let idx = i as usize;
|
||||
let Vec2 {
|
||||
x: continent_x,
|
||||
y: continent_y,
|
||||
} = self.continent_offsets[idx];
|
||||
|
||||
let distance_x = f32::min(
|
||||
f32::min((continent_x - x).abs(), (width + continent_x - x).abs()),
|
||||
(continent_x - x - width).abs(),
|
||||
) * beta_factor;
|
||||
|
||||
let distance_y = f32::abs(continent_y - y);
|
||||
|
||||
let distance = (distance_x * distance_x + distance_y * distance_y).sqrt();
|
||||
|
||||
let value = f32::max(0.0, 1.0 - self.continent_widths[idx] * distance / width);
|
||||
|
||||
max_value = f32::max(max_value, value);
|
||||
}
|
||||
|
||||
max_value
|
||||
}
|
||||
|
||||
fn generate_altitude(&mut self) -> Result<(), CartesianError> {
|
||||
info!("Generating altitude");
|
||||
self.generate_continents();
|
||||
|
||||
const RADIUS_1: f32 = 0.5;
|
||||
const RADIUS_2: f32 = 4.0;
|
||||
const RADIUS_3: f32 = 4.0;
|
||||
const RADIUS_4: f32 = 8.0;
|
||||
const RADIUS_5: f32 = 16.0;
|
||||
|
||||
let offset_1 = Self::random_offset_vector();
|
||||
let offset_2 = Self::random_offset_vector();
|
||||
let offset_3 = Self::random_offset_vector();
|
||||
let offset_4 = Self::random_offset_vector();
|
||||
let offset_5 = Self::random_offset_vector();
|
||||
|
||||
for y in 0..self.terrain.len() {
|
||||
let alpha = (y as f32 / self.height as f32) * PI;
|
||||
|
||||
info!("{}/{}", y, self.terrain.len());
|
||||
|
||||
for x in 0..self.terrain[y].len() {
|
||||
let beta = (x as f32 / self.width as f32) * TAU;
|
||||
|
||||
let value_1 =
|
||||
self.random_noise_from_polar_coordinates(alpha, beta, RADIUS_1, offset_1)?;
|
||||
let value_2 =
|
||||
self.random_noise_from_polar_coordinates(alpha, beta, RADIUS_2, offset_2)?;
|
||||
let value_3 =
|
||||
self.random_noise_from_polar_coordinates(alpha, beta, RADIUS_3, offset_3)?;
|
||||
let value_4 =
|
||||
self.random_noise_from_polar_coordinates(alpha, beta, RADIUS_4, offset_4)?;
|
||||
let value_5 =
|
||||
self.random_noise_from_polar_coordinates(alpha, beta, RADIUS_5, offset_5)?;
|
||||
|
||||
let continent_value = self.continent_modifier(x, y);
|
||||
|
||||
let mut value_continent = continent_value;
|
||||
value_continent =
|
||||
mix_values(value_continent, value_3, Self::TERRAIN_NOISE_FACTOR_1);
|
||||
value_continent *= mix_values(1.0, value_4, Self::TERRAIN_NOISE_FACTOR_2);
|
||||
|
||||
let value_b = mix_values(1.0, value_5, Self::TERRAIN_NOISE_FACTOR_3);
|
||||
|
||||
let mut value_mountain =
|
||||
mix_values(value_1, value_2, Self::MOUNTAIN_RANGE_MIX_FACTOR);
|
||||
value_mountain = self.mountain_range_noise_from_random_noise(value_mountain);
|
||||
value_mountain =
|
||||
mix_values(value_mountain, value_3, Self::TERRAIN_NOISE_FACTOR_1 * 1.5);
|
||||
value_mountain *= mix_values(1.0, value_4, Self::TERRAIN_NOISE_FACTOR_2 * 1.5);
|
||||
value_mountain *= mix_values(1.0, value_5, Self::TERRAIN_NOISE_FACTOR_3 * 1.5);
|
||||
|
||||
let mut raw_altitude = mix_values(value_continent, value_mountain, 0.25);
|
||||
raw_altitude = mix_values(raw_altitude, value_mountain, 0.1);
|
||||
raw_altitude = mix_values(raw_altitude, value_b, 0.1);
|
||||
|
||||
self.terrain[y][x].altitude = Self::calculate_altitude(raw_altitude);
|
||||
}
|
||||
}
|
||||
info!("Done generating altitude");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn random_offset_vector() -> Vec3A {
|
||||
random_point_in_sphere(1000.0)
|
||||
}
|
||||
|
||||
fn random_noise_from_polar_coordinates(
|
||||
&self,
|
||||
alpha: f32,
|
||||
beta: f32,
|
||||
radius: f32,
|
||||
offset: Vec3A,
|
||||
) -> Result<f32, CartesianError> {
|
||||
let offset = cartesian_coordinates(alpha, beta, radius)? + offset;
|
||||
Ok(self
|
||||
.perlin
|
||||
.get([offset.x as f64, offset.y as f64, offset.z as f64]) as f32)
|
||||
}
|
||||
|
||||
fn mountain_range_noise_from_random_noise(&self, noise: f32) -> f32 {
|
||||
let noise = noise * 2.0 - 1.0;
|
||||
|
||||
let value_1 = -f32::exp(-f32::powi(
|
||||
noise * Self::MOUNTAIN_RANGE_WIDTH_FACTOR + 1.0,
|
||||
2,
|
||||
));
|
||||
let value_2 = f32::exp(-f32::powi(
|
||||
noise * Self::MOUNTAIN_RANGE_WIDTH_FACTOR - 1.0,
|
||||
2,
|
||||
));
|
||||
let value_3 = -f32::exp(-f32::powi(
|
||||
noise * Self::MOUNTAIN_RANGE_WIDTH_FACTOR + Self::MOUNTAIN_RANGE_WIDTH_FACTOR / 2.0,
|
||||
2,
|
||||
));
|
||||
let value_4 = f32::exp(-f32::powi(
|
||||
noise * Self::MOUNTAIN_RANGE_WIDTH_FACTOR - Self::MOUNTAIN_RANGE_WIDTH_FACTOR / 2.0,
|
||||
2,
|
||||
));
|
||||
|
||||
(value_1 + value_2 + value_3 + value_4 + 1.0) / 2.0
|
||||
}
|
||||
|
||||
fn calculate_altitude(raw_altitude: f32) -> f32 {
|
||||
Self::MIN_ALTITUDE + (raw_altitude * Self::ALTITUDE_SPAN)
|
||||
}
|
||||
|
||||
/*
|
||||
fn generate_rainfall_alt(&mut self) -> Result<(), CartesianError> {
|
||||
let max_cycles = self.width / 5;
|
||||
|
||||
const ACCUMULATED_RAIN_FACTOR: f32 = 0.06;
|
||||
const RAINFALL_FACTOR: f32 = 0.005;
|
||||
const RAINFALL_ALTITUDE_FACTOR: f32 = 0.05;
|
||||
|
||||
for _ in 0..max_cycles {
|
||||
for x in 0..self.width {
|
||||
for y in 0..self.height {
|
||||
let mut prev_x = (x - 1 + self.width) % self.width;
|
||||
let prev_y = if y < self.height / 4 {
|
||||
y + 1
|
||||
} else if y < self.height / 2 {
|
||||
prev_x = (x + 1) % self.width;
|
||||
y - 1
|
||||
} else if y < (self.height * 3) / 4 {
|
||||
prev_x = (x + 1) % self.width;
|
||||
y + 1
|
||||
} else {
|
||||
y - 1
|
||||
};
|
||||
|
||||
let mut cell = self.terrain[y as usize][x as usize];
|
||||
cell.previous_rain_accumulated = cell.rain_accumulated;
|
||||
cell.rain_accumulated = 0.0;
|
||||
|
||||
if cell.altitude <= 0.0 {
|
||||
cell.rain_accumulated += ACCUMULATED_RAIN_FACTOR * Self::MAX_RAINFALL;
|
||||
}
|
||||
|
||||
let prev_cell = self.terrain[prev_y as usize][prev_x as usize];
|
||||
|
||||
let altitude_difference =
|
||||
f32::max(0.0, cell.altitude) - f32::max(0.0, prev_cell.altitude);
|
||||
let altitude_factor = f32::max(
|
||||
0.0,
|
||||
RAINFALL_ALTITUDE_FACTOR * altitude_difference / Self::MAX_ALTITUDE,
|
||||
);
|
||||
let final_rain_factor = f32::min(1.0, RAINFALL_FACTOR + altitude_factor);
|
||||
|
||||
cell.rain_accumulated += prev_cell.previous_rain_accumulated;
|
||||
cell.rain_accumulated =
|
||||
f32::min(cell.rain_accumulated, Self::MAX_RAINFALL / RAINFALL_FACTOR);
|
||||
|
||||
let rain_accumulated = cell.rain_accumulated * final_rain_factor;
|
||||
cell.rainfall += rain_accumulated;
|
||||
cell.rain_accumulated -= rain_accumulated;
|
||||
|
||||
cell.rain_accumulated = f32::max(cell.rain_accumulated, 0.0);
|
||||
|
||||
self.terrain[y as usize][x as usize] = cell;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
*/
|
||||
|
||||
fn generate_rainfall(&mut self) -> Result<(), CartesianError> {
|
||||
let offset = Self::random_offset_vector();
|
||||
const RADIUS: f32 = 2.0;
|
||||
|
||||
for y in 0..self.terrain.len() {
|
||||
let alpha = (y as f32 / self.height as f32) * PI;
|
||||
for x in 0..self.terrain[y].len() {
|
||||
let beta = (x as f32 / self.width as f32) * TAU;
|
||||
|
||||
let random_noise =
|
||||
self.random_noise_from_polar_coordinates(alpha, beta, RADIUS, offset)?;
|
||||
|
||||
let mut cell = &mut self.terrain[y][x];
|
||||
|
||||
let base_rainfall = Self::calculate_rainfall(random_noise);
|
||||
let altitude_factor = f32::clamp(
|
||||
(cell.altitude / Self::MAX_ALTITUDE) * Self::RAINFALL_ALTITUDE_FACTOR,
|
||||
0.0,
|
||||
1.0,
|
||||
);
|
||||
let rainfall = base_rainfall * (1.0 - altitude_factor);
|
||||
|
||||
cell.rainfall = rainfall;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn calculate_rainfall(raw_rainfall: f32) -> f32 {
|
||||
f32::clamp(
|
||||
(raw_rainfall * Self::RAINFALL_SPAN) + Self::MIN_RAINFALL,
|
||||
0.0,
|
||||
Self::MAX_RAINFALL,
|
||||
)
|
||||
}
|
||||
|
||||
fn generate_temperature(&mut self) -> Result<(), CartesianError> {
|
||||
let offset = Self::random_offset_vector();
|
||||
const RADIUS: f32 = 2.0;
|
||||
|
||||
for y in 0..self.terrain.len() {
|
||||
let alpha = (y as f32 / self.height as f32) * PI;
|
||||
for x in 0..self.terrain[y].len() {
|
||||
let beta = (x as f32 / self.width as f32) * TAU;
|
||||
|
||||
let random_noise =
|
||||
self.random_noise_from_polar_coordinates(alpha, beta, RADIUS, offset)?;
|
||||
|
||||
let cell = &mut self.terrain[y][x];
|
||||
|
||||
let altitude_factor = (cell.altitude / Self::MAX_ALTITUDE)
|
||||
* (2.5 * Self::TEMPERATURE_ALTITUDE_FACTOR);
|
||||
|
||||
let latitude_modifer = (alpha * 0.8) + (random_noise * 0.2 * PI);
|
||||
let base_temperature = Self::calculate_temperature(f32::sin(latitude_modifer));
|
||||
|
||||
cell.temperature = base_temperature * (1.0 - altitude_factor);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn calculate_temperature(raw_temperature: f32) -> f32 {
|
||||
f32::clamp(
|
||||
(raw_temperature * Self::TEMPERATURE_SPAN) + Self::MIN_TEMPERATURE,
|
||||
Self::MIN_TEMPERATURE,
|
||||
Self::MAX_TEMPERATURE,
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,258 +0,0 @@
|
|||
#[cfg(feature = "render")]
|
||||
use crate::TerrainCell;
|
||||
use crate::{World, WorldGenError};
|
||||
use bevy::log::error;
|
||||
#[cfg(all(feature = "debug", feature = "render"))]
|
||||
use bevy::log::{debug, info};
|
||||
#[cfg(feature = "render")]
|
||||
use bevy::{
|
||||
asset::HandleId,
|
||||
render::{color::Color, texture::Image},
|
||||
};
|
||||
use rand::random;
|
||||
use std::{
|
||||
fs::File,
|
||||
io::{Read, Write},
|
||||
path::Path,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct WorldManager {
|
||||
#[cfg(feature = "render")]
|
||||
pub image_handle_id: HandleId,
|
||||
world: Option<World>,
|
||||
#[cfg(feature = "render")]
|
||||
rainfall_visible: bool,
|
||||
#[cfg(feature = "render")]
|
||||
temperature_visible: bool,
|
||||
#[cfg(feature = "render")]
|
||||
terrain_as_contours: bool,
|
||||
}
|
||||
|
||||
impl WorldManager {
|
||||
pub fn new() -> WorldManager {
|
||||
Self {
|
||||
#[cfg(feature = "render")]
|
||||
image_handle_id: HandleId::default::<Image>(),
|
||||
world: None,
|
||||
#[cfg(feature = "render")]
|
||||
rainfall_visible: false,
|
||||
#[cfg(feature = "render")]
|
||||
temperature_visible: false,
|
||||
#[cfg(feature = "render")]
|
||||
terrain_as_contours: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn save_world<P: AsRef<Path>>(&self, path: P) {
|
||||
let world = match self.get_world() {
|
||||
Some(world) => world,
|
||||
None => {
|
||||
info!("No world to save");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let serialized = match ron::to_string(world) {
|
||||
Ok(serialized) => serialized,
|
||||
Err(err) => {
|
||||
error!("Could not serialize world: {}", err);
|
||||
return;
|
||||
}
|
||||
};
|
||||
File::create(path).unwrap().write_all(serialized.as_bytes());
|
||||
}
|
||||
|
||||
pub fn load_world<P: AsRef<Path>>(&mut self, path: P) {
|
||||
let mut file = match File::open(path) {
|
||||
Ok(file) => file,
|
||||
Err(err) => {
|
||||
error!("Could not open world file: {}", err);
|
||||
return;
|
||||
}
|
||||
};
|
||||
let mut buf = String::new();
|
||||
file.read_to_string(&mut buf);
|
||||
match ron::from_str(buf.as_str()) {
|
||||
Ok(world) => self.world = Some(world),
|
||||
Err(err) => {
|
||||
error!("Could not deserialize world: {}", err);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "render")]
|
||||
pub fn toggle_rainfall(&mut self) {
|
||||
#[cfg(feature = "debug")]
|
||||
if self.rainfall_visible {
|
||||
debug!("Turning rainfall off");
|
||||
} else {
|
||||
debug!("Turning rainfall on");
|
||||
}
|
||||
self.rainfall_visible = !self.rainfall_visible;
|
||||
}
|
||||
|
||||
#[cfg(feature = "render")]
|
||||
pub fn toggle_temperature(&mut self) {
|
||||
#[cfg(feature = "debug")]
|
||||
if self.temperature_visible {
|
||||
debug!("Turning temperature off");
|
||||
} else {
|
||||
debug!("Turning temperature on");
|
||||
}
|
||||
self.temperature_visible = !self.temperature_visible;
|
||||
}
|
||||
|
||||
#[cfg(feature = "render")]
|
||||
pub fn toggle_contours(&mut self) {
|
||||
#[cfg(feature = "debug")]
|
||||
if self.terrain_as_contours {
|
||||
debug!("Turning terrain contours off");
|
||||
} else {
|
||||
debug!("Turning terrain contours on");
|
||||
}
|
||||
self.terrain_as_contours = !self.terrain_as_contours;
|
||||
}
|
||||
|
||||
pub fn get_world(&self) -> Option<&World> {
|
||||
self.world.as_ref()
|
||||
}
|
||||
pub fn world(&self) -> &World {
|
||||
self.get_world().unwrap()
|
||||
}
|
||||
|
||||
pub fn new_world(&mut self) -> Result<&World, WorldGenError> {
|
||||
let seed = random();
|
||||
let mut new_world = World::new(400, 200, seed);
|
||||
new_world.generate()?;
|
||||
self.world = Some(new_world);
|
||||
Ok(self.get_world().unwrap())
|
||||
}
|
||||
|
||||
#[cfg(feature = "render")]
|
||||
fn generate_color(&self, cell: &TerrainCell) -> Color {
|
||||
let mut final_color = if self.terrain_as_contours {
|
||||
Self::altitude_contour_color(cell.altitude)
|
||||
} else {
|
||||
Self::altitude_color(cell.altitude)
|
||||
};
|
||||
|
||||
if self.rainfall_visible {
|
||||
let rainfall_color = Self::rainfall_color(cell.rainfall);
|
||||
let normalized_rainfall = Self::normalize_rainfall(cell.rainfall);
|
||||
|
||||
_ = final_color.set_r(
|
||||
(final_color.r() * (1.0 - normalized_rainfall))
|
||||
+ (rainfall_color.r() * normalized_rainfall),
|
||||
);
|
||||
_ = final_color.set_g(
|
||||
(final_color.g() * (1.0 - normalized_rainfall))
|
||||
+ (rainfall_color.g() * normalized_rainfall),
|
||||
);
|
||||
_ = final_color.set_b(
|
||||
(final_color.b() * (1.0 - normalized_rainfall))
|
||||
+ (rainfall_color.b() * normalized_rainfall),
|
||||
);
|
||||
}
|
||||
|
||||
if self.temperature_visible {
|
||||
let temperature_color = Self::temperature_color(cell.temperature);
|
||||
let normalized_temperature = Self::normalize_temperature(cell.temperature);
|
||||
|
||||
_ = final_color.set_r(
|
||||
(final_color.r() * (1.0 - normalized_temperature))
|
||||
+ (temperature_color.r() * normalized_temperature),
|
||||
);
|
||||
_ = final_color.set_g(
|
||||
(final_color.g() * (1.0 - normalized_temperature))
|
||||
+ (temperature_color.g() * normalized_temperature),
|
||||
);
|
||||
_ = final_color.set_b(
|
||||
(final_color.b() * (1.0 - normalized_temperature))
|
||||
+ (temperature_color.b() * normalized_temperature),
|
||||
);
|
||||
}
|
||||
|
||||
final_color
|
||||
}
|
||||
|
||||
#[cfg(feature = "render")]
|
||||
fn altitude_color(altitude: f32) -> Color {
|
||||
if altitude < 0.0 {
|
||||
Color::rgb(0.0, 0.0, (2.0 - altitude / World::MIN_ALTITUDE) / 2.0)
|
||||
} else {
|
||||
let mult = (1.0 + altitude / World::MAX_ALTITUDE) / 2.0;
|
||||
|
||||
Color::rgb(0.58 * mult, 0.29 * mult, 0.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "render")]
|
||||
fn altitude_contour_color(altitude: f32) -> Color {
|
||||
if altitude < 0.0 {
|
||||
Color::rgb(0.0, 0.0, (2.0 - altitude / World::MIN_ALTITUDE) / 2.0)
|
||||
} else {
|
||||
let mut shade_value = 1.0;
|
||||
|
||||
while shade_value > altitude / World::MAX_ALTITUDE {
|
||||
shade_value -= 0.05;
|
||||
}
|
||||
|
||||
Color::rgb(shade_value, shade_value, shade_value)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "render")]
|
||||
fn rainfall_color(rainfall: f32) -> Color {
|
||||
Color::rgb(0.0, Self::normalize_rainfall(rainfall), 0.0)
|
||||
}
|
||||
|
||||
#[cfg(feature = "render")]
|
||||
fn normalize_rainfall(rainfall: f32) -> f32 {
|
||||
if rainfall <= 0.0 {
|
||||
0.0
|
||||
} else {
|
||||
rainfall / World::MAX_RAINFALL
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "render")]
|
||||
fn temperature_color(temperature: f32) -> Color {
|
||||
let normalized_temperature = Self::normalize_temperature(temperature);
|
||||
Color::rgb(normalized_temperature, 1.0 - normalized_temperature, 0.0)
|
||||
}
|
||||
|
||||
#[cfg(feature = "render")]
|
||||
fn normalize_temperature(temperature: f32) -> f32 {
|
||||
(temperature - World::MIN_TEMPERATURE) / World::TEMPERATURE_SPAN
|
||||
}
|
||||
|
||||
#[cfg(feature = "render")]
|
||||
pub fn world_colors(&self) -> Vec<Color> {
|
||||
match self.get_world() {
|
||||
None => panic!("Called world_colors before generating world"),
|
||||
Some(world) => {
|
||||
let terrain_cells: Vec<_> = world.terrain.iter().rev().flatten().collect();
|
||||
|
||||
terrain_cells
|
||||
.iter()
|
||||
.map(|cell| self.generate_color(cell))
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "render")]
|
||||
pub fn world_color_bytes(&self) -> Vec<u8> {
|
||||
self.world_colors()
|
||||
.iter()
|
||||
.flat_map(|color| {
|
||||
color
|
||||
.as_rgba_f32()
|
||||
.iter()
|
||||
.flat_map(|num| num.to_le_bytes())
|
||||
.collect::<Vec<u8>>()
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
|
@ -25,6 +25,7 @@ define_enum!(ToolbarButton {
|
|||
Contours,
|
||||
});
|
||||
|
||||
#[cfg(feature = "render")]
|
||||
impl From<ToolbarButton> for &'static str {
|
||||
fn from(button: ToolbarButton) -> Self {
|
||||
match button {
|
||||
|
@ -37,6 +38,7 @@ impl From<ToolbarButton> for &'static str {
|
|||
}
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "render")]
|
||||
impl From<&ToolbarButton> for &'static str {
|
||||
fn from(button: &ToolbarButton) -> Self {
|
||||
match button {
|
||||
|
@ -50,12 +52,14 @@ impl From<&ToolbarButton> for &'static str {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "render")]
|
||||
impl From<ToolbarButton> for String {
|
||||
fn from(button: ToolbarButton) -> Self {
|
||||
<&'static str>::from(button).into()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "render")]
|
||||
impl From<&ToolbarButton> for String {
|
||||
fn from(button: &ToolbarButton) -> Self {
|
||||
<&'static str>::from(button).into()
|
||||
|
|
77
src/main.rs
77
src/main.rs
|
@ -93,10 +93,10 @@ use components::{
|
|||
markers::{InfoPanel, ToolbarButton},
|
||||
third_party::PanCam,
|
||||
};
|
||||
use planet::WorldManager;
|
||||
use plugins::WorldPlugins;
|
||||
#[cfg(feature = "render")]
|
||||
use resources::CursorMapPosition;
|
||||
use save::*;
|
||||
#[cfg(feature = "render")]
|
||||
use ui_helpers::{toolbar_button, toolbar_button_text};
|
||||
|
||||
|
@ -104,8 +104,16 @@ use ui_helpers::{toolbar_button, toolbar_button_text};
|
|||
fn refresh_world_texture(images: &mut Assets<Image>, world_manager: &WorldManager) {
|
||||
#[cfg(feature = "debug")]
|
||||
debug!("refreshing world texture");
|
||||
let image_handle = images.get_handle(world_manager.image_handle_id);
|
||||
images.get_mut(&image_handle).unwrap().data = world_manager.world_color_bytes();
|
||||
let image_handle = images.get_handle(world_manager.image_handle_id.expect("No image handle"));
|
||||
let world_image = images
|
||||
.get_mut(&image_handle)
|
||||
.expect("Image handle pointing to non-existing texture");
|
||||
world_image.resize(Extent3d {
|
||||
width: world_manager.world().width,
|
||||
height: world_manager.world().height,
|
||||
depth_or_array_layers: 1,
|
||||
});
|
||||
world_image.data = world_manager.world_color_bytes();
|
||||
|
||||
// TODO: Update Icosphere material... try to find out why it doesn't automatically=
|
||||
}
|
||||
|
@ -138,16 +146,19 @@ fn handle_toolbar_button(
|
|||
#[cfg(feature = "debug")]
|
||||
debug!("Toggling rainfall");
|
||||
world_manager.toggle_rainfall();
|
||||
refresh_world_texture(&mut images, &world_manager);
|
||||
}
|
||||
ToolbarButton::Temperature => {
|
||||
#[cfg(feature = "debug")]
|
||||
debug!("Toggling temperature");
|
||||
world_manager.toggle_temperature();
|
||||
refresh_world_texture(&mut images, &world_manager);
|
||||
}
|
||||
ToolbarButton::Contours => {
|
||||
#[cfg(feature = "debug")]
|
||||
debug!("Toggling contours");
|
||||
world_manager.toggle_contours();
|
||||
refresh_world_texture(&mut images, &world_manager);
|
||||
}
|
||||
ToolbarButton::GenerateWorld => {
|
||||
#[cfg(feature = "debug")]
|
||||
|
@ -155,6 +166,7 @@ fn handle_toolbar_button(
|
|||
_ = world_manager
|
||||
.new_world()
|
||||
.expect("Failed to generate new world");
|
||||
refresh_world_texture(&mut images, &world_manager);
|
||||
}
|
||||
ToolbarButton::SaveWorld => {
|
||||
#[cfg(feature = "debug")]
|
||||
|
@ -164,10 +176,10 @@ fn handle_toolbar_button(
|
|||
ToolbarButton::LoadWorld => {
|
||||
#[cfg(feature = "debug")]
|
||||
debug!("Loading world");
|
||||
_ = world_manager.load_world("planet.ron");
|
||||
_ = world_manager.load_world("planet.ron", &mut images);
|
||||
refresh_world_texture(&mut images, &world_manager);
|
||||
}
|
||||
}
|
||||
refresh_world_texture(&mut images, &world_manager)
|
||||
}
|
||||
Interaction::Hovered => {
|
||||
windows.primary_mut().set_cursor_icon(CursorIcon::Hand);
|
||||
|
@ -208,8 +220,8 @@ fn update_cursor_map_position(
|
|||
ndc_to_world.project_point3(ndc.extend(-1.0)).truncate() / WORLD_SCALE as f32;
|
||||
|
||||
let world = world_manager.world();
|
||||
cursor_map_position.x = world_position.x as i32 + world.width / 2 - 1;
|
||||
cursor_map_position.y = world.height / 2 - world_position.y as i32 - 1;
|
||||
cursor_map_position.x = world.width as i32 / 2 + f32::ceil(world_position.x) as i32 - 1;
|
||||
cursor_map_position.y = world.height as i32 / 2 + f32::ceil(world_position.y) as i32 - 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -229,11 +241,12 @@ fn update_info_panel(
|
|||
) {
|
||||
let world = world_manager.world();
|
||||
text.single_mut().sections[0].value = if cursor_position.x >= 0
|
||||
&& cursor_position.x < world.width
|
||||
&& cursor_position.x < world.width as i32
|
||||
&& cursor_position.y >= 0
|
||||
&& cursor_position.y < world.height
|
||||
&& cursor_position.y < world.height as i32
|
||||
{
|
||||
let cell = &world.terrain[cursor_position.y as usize][cursor_position.x as usize];
|
||||
|
||||
#[cfg(feature = "debug")]
|
||||
{
|
||||
format!(
|
||||
|
@ -248,6 +261,7 @@ fn update_info_panel(
|
|||
cell.temperature
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "debug"))]
|
||||
{
|
||||
format!(
|
||||
|
@ -256,14 +270,22 @@ fn update_info_panel(
|
|||
)
|
||||
}
|
||||
} else {
|
||||
format!(
|
||||
"FPS: ~{}\nMouse position: {}\nOut of bounds",
|
||||
match diagnostics.get_measurement(FrameTimeDiagnosticsPlugin::FPS) {
|
||||
None => f64::NAN,
|
||||
Some(fps) => fps.value.round(),
|
||||
},
|
||||
*cursor_position
|
||||
)
|
||||
#[cfg(feature = "debug")]
|
||||
{
|
||||
format!(
|
||||
"FPS: ~{}\nMouse position: {}\nOut of bounds",
|
||||
match diagnostics.get_measurement(FrameTimeDiagnosticsPlugin::FPS) {
|
||||
None => f64::NAN,
|
||||
Some(fps) => fps.value.round(),
|
||||
},
|
||||
*cursor_position
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "debug"))]
|
||||
{
|
||||
format!("Mouse position: {}\nOut of bounds", *cursor_position)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -278,8 +300,8 @@ fn generate_graphics(
|
|||
) {
|
||||
let world = world_manager.world();
|
||||
let custom_sprite_size = Vec2 {
|
||||
x: (WORLD_SCALE * world.width) as f32,
|
||||
y: (WORLD_SCALE * world.height) as f32,
|
||||
x: (WORLD_SCALE * world.width as i32) as f32,
|
||||
y: (WORLD_SCALE * world.height as i32) as f32,
|
||||
};
|
||||
|
||||
let image_handle = images.add(Image {
|
||||
|
@ -287,8 +309,8 @@ fn generate_graphics(
|
|||
texture_descriptor: TextureDescriptor {
|
||||
label: None,
|
||||
size: Extent3d {
|
||||
width: world.width as u32,
|
||||
height: world.height as u32,
|
||||
width: world.width,
|
||||
height: world.height,
|
||||
..default()
|
||||
},
|
||||
dimension: TextureDimension::D2,
|
||||
|
@ -299,7 +321,7 @@ fn generate_graphics(
|
|||
},
|
||||
..default()
|
||||
});
|
||||
world_manager.image_handle_id = image_handle.id;
|
||||
world_manager.image_handle_id = Some(image_handle.id);
|
||||
|
||||
#[cfg(feature = "planet_view")]
|
||||
{
|
||||
|
@ -338,9 +360,12 @@ fn generate_graphics(
|
|||
|
||||
_ = commands
|
||||
.spawn_bundle(Camera2dBundle { ..default() })
|
||||
.insert(PanCam::default());
|
||||
.insert(PanCam {
|
||||
max_scale: Some(80.0),
|
||||
..default()
|
||||
});
|
||||
_ = commands.spawn_bundle(SpriteBundle {
|
||||
texture: images.get_handle(world_manager.image_handle_id),
|
||||
texture: images.get_handle(world_manager.image_handle_id.unwrap()),
|
||||
sprite: Sprite {
|
||||
custom_size: Some(custom_sprite_size),
|
||||
..default()
|
||||
|
@ -423,8 +448,8 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||
// Use nearest-neighbor rendering for cripsier pixels
|
||||
.insert_resource(ImageSettings::default_nearest())
|
||||
.insert_resource(WindowDescriptor {
|
||||
width: (WORLD_SCALE * world.width) as f32,
|
||||
height: (WORLD_SCALE * world.height) as f32,
|
||||
width: (WORLD_SCALE * world.width as i32) as f32,
|
||||
height: (WORLD_SCALE * world.height as i32) as f32,
|
||||
title: String::from("World-RS"),
|
||||
resizable: true,
|
||||
..default()
|
||||
|
|
|
@ -3,7 +3,7 @@ pub(crate) struct WorldPlugins;
|
|||
use bevy::{
|
||||
app::{PluginGroup, PluginGroupBuilder},
|
||||
core::CorePlugin,
|
||||
diagnostic::{DiagnosticsPlugin, FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin},
|
||||
diagnostic::{DiagnosticsPlugin, LogDiagnosticsPlugin},
|
||||
log::LogPlugin,
|
||||
time::TimePlugin,
|
||||
};
|
||||
|
@ -43,10 +43,6 @@ impl PluginGroup for WorldPlugins {
|
|||
use bevy::pbr::PbrPlugin;
|
||||
_ = group.add(PbrPlugin::default())
|
||||
}
|
||||
#[cfg(feature = "debug")]
|
||||
{
|
||||
_ = group.add(FrameTimeDiagnosticsPlugin::default());
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "render"))]
|
||||
{
|
||||
|
@ -54,9 +50,12 @@ impl PluginGroup for WorldPlugins {
|
|||
_ = group.add(ScheduleRunnerPlugin::default());
|
||||
}
|
||||
|
||||
_ = group
|
||||
.add(DiagnosticsPlugin::default())
|
||||
.add(FrameTimeDiagnosticsPlugin::default())
|
||||
.add(LogDiagnosticsPlugin::default());
|
||||
_ = group.add(DiagnosticsPlugin::default());
|
||||
#[cfg(all(feature = "debug"))]
|
||||
{
|
||||
use bevy::diagnostic::FrameTimeDiagnosticsPlugin;
|
||||
_ = group.add(FrameTimeDiagnosticsPlugin::default());
|
||||
}
|
||||
_ = group.add(LogDiagnosticsPlugin::default());
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue