Tweak rain gen; Fix seeding; Rename save->planet; Various other fixes

0c641573543d4d503e8c2d7e33b246709d7eface
This commit is contained in:
Tobias Berger 2022-09-18 17:30:04 +02:00
parent 39ecfb3cc2
commit 8df06365a7
Signed by: toby
GPG key ID: 2D05EFAB764D6A88
16 changed files with 1309 additions and 894 deletions

188
Cargo.lock generated
View file

@ -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]]

View file

@ -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

View file

@ -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"

View file

@ -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;

View file

@ -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
View 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
View 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
View 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
View 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()
}
}

View file

@ -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,
)
}
}

View file

@ -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()
}
}

View file

@ -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()

View file

@ -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,6 +270,8 @@ fn update_info_panel(
)
}
} else {
#[cfg(feature = "debug")]
{
format!(
"FPS: ~{}\nMouse position: {}\nOut of bounds",
match diagnostics.get_measurement(FrameTimeDiagnosticsPlugin::FPS) {
@ -264,6 +280,12 @@ fn update_info_panel(
},
*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()

View file

@ -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());
}
}