diff --git a/planet/src/human_group.rs b/planet/src/human_group.rs new file mode 100644 index 0000000..c4f9898 --- /dev/null +++ b/planet/src/human_group.rs @@ -0,0 +1,7 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize)] +pub struct HumanGroup { + pub id: u32, + pub population: u32, +} diff --git a/planet/src/lib.rs b/planet/src/lib.rs index 653243d..15dfbb0 100644 --- a/planet/src/lib.rs +++ b/planet/src/lib.rs @@ -1,3 +1,4 @@ +pub mod human_group; pub mod world; pub use world::{TerrainCell, World, WorldGenError}; pub mod biome; diff --git a/planet/src/perlin.rs b/planet/src/perlin.rs index 23d8903..048826c 100644 --- a/planet/src/perlin.rs +++ b/planet/src/perlin.rs @@ -1,4 +1,17 @@ -const PERMUTATION: [u8; 256] = [ +const PERMUTATION: [u8; 512] = [ + 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, // Duplicated 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, @@ -15,9 +28,28 @@ const PERMUTATION: [u8; 256] = [ ]; #[must_use] -pub fn perlin_value(x: f32, y: f32, z: f32) -> f32 { - let p = [PERMUTATION, PERMUTATION].concat(); +pub fn permutation_value(x: f32, y: f32, z: f32) -> u32 { + let x = (f32::floor(x) as i32 & 255) as usize; + let y = (f32::floor(y) as i32 & 255) as usize; + let z = (f32::floor(z) as i32 & 255) as usize; + let a = PERMUTATION[x] as usize + y; + let aa = PERMUTATION[a] as usize + z; + let ab = PERMUTATION[aa] as u32; + + let b = PERMUTATION[x + 1] as usize + y; + let ba = PERMUTATION[b] as usize + z; + let bb = PERMUTATION[ba] as u32; + + let c = PERMUTATION[x + 2] as usize + y; + let ca = PERMUTATION[c] as usize + z; + let cb = PERMUTATION[ca] as u32; + + ab + (bb * 256) + (cb * 256 * 256) +} + +#[must_use] +pub fn perlin_value(x: f32, y: f32, z: f32) -> f32 { let fx: i32 = f32::floor(x) as i32; let fy: i32 = f32::floor(y) as i32; let fz: i32 = f32::floor(z) as i32; @@ -34,36 +66,40 @@ pub fn perlin_value(x: f32, y: f32, z: f32) -> f32 { 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 a = PERMUTATION[xb] as usize + yb; + let aa = PERMUTATION[a] as usize + zb; + let ab = PERMUTATION[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; + let b = PERMUTATION[xb + 1] as usize + yb; + let ba = PERMUTATION[b] as usize + zb; + let bb = PERMUTATION[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), + grad(PERMUTATION[aa], x, y, z), + grad(PERMUTATION[ba], x - 1.0, y, z), + ), + lerp( + u, + grad(PERMUTATION[ab], x, y - 1.0, z), + grad(PERMUTATION[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), + grad(PERMUTATION[aa + 1], x, y, z - 1.0), + grad(PERMUTATION[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), + grad(PERMUTATION[ab + 1], x, y - 1.0, z - 1.0), + grad(PERMUTATION[bb + 1], x - 1.0, y - 1.0, z - 1.0), ), ), )) diff --git a/planet/src/saving/mod.rs b/planet/src/saving/mod.rs new file mode 100644 index 0000000..d93db9f --- /dev/null +++ b/planet/src/saving/mod.rs @@ -0,0 +1,2 @@ +// pub mod terrain_cell; +pub mod world; diff --git a/planet/src/saving.rs b/planet/src/saving/world.rs similarity index 80% rename from planet/src/saving.rs rename to planet/src/saving/world.rs index 724a709..799cb04 100644 --- a/planet/src/saving.rs +++ b/planet/src/saving/world.rs @@ -1,5 +1,6 @@ use { crate::{TerrainCell, World}, + bevy::prelude::debug, rand::{rngs::StdRng, SeedableRng}, serde::{ de::{Error, MapAccess, SeqAccess, Visitor}, @@ -43,6 +44,7 @@ impl<'de> Deserialize<'de> for World { Terrain, ContinentOffsets, ContinentSizes, + Iteration, } struct WorldVisitor; @@ -64,23 +66,29 @@ impl<'de> Deserialize<'de> for World { let height = seq .next_element()? - .ok_or_else(|| Error::invalid_length(0, &self))?; + .ok_or_else(|| Error::invalid_length(1, &self))?; let seed = seq .next_element()? - .ok_or_else(|| Error::invalid_length(0, &self))?; + .ok_or_else(|| Error::invalid_length(2, &self))?; let terrain: Vec> = seq .next_element()? - .ok_or_else(|| Error::invalid_length(0, &self))?; + .ok_or_else(|| Error::invalid_length(3, &self))?; let continent_offsets = seq .next_element()? - .ok_or_else(|| Error::invalid_length(0, &self))?; + .ok_or_else(|| Error::invalid_length(4, &self))?; - let continent_widths = seq + let continent_sizes = seq .next_element()? - .ok_or_else(|| Error::invalid_length(0, &self))?; + .ok_or_else(|| Error::invalid_length(5, &self))?; + + debug!("Iteration aaaaa"); + let iteration = seq + .next_element()? + .ok_or_else(|| Error::invalid_length(6, &self))?; + debug!("Iteration bbbbb"); let world_attributes = &mut WorldTerrainAttributes::default(); let world_attributes = @@ -111,13 +119,14 @@ impl<'de> Deserialize<'de> for World { attributes }); - Ok(World { + debug!("Constructing world"); + let mut world = World { width, height, seed, terrain, continent_offsets, - continent_sizes: continent_widths, + continent_sizes, max_altitude: world_attributes.max_altitude, min_altitude: world_attributes.min_altitude, @@ -127,7 +136,22 @@ impl<'de> Deserialize<'de> for World { min_temperature: world_attributes.min_temperature, rng: StdRng::seed_from_u64(seed as u64), - }) + iteration, + }; + { + let mut y = 0; + debug!("Completing terrain"); + for terrain_row in world.terrain.iter_mut() { + let mut x = 0; + for terrain_cell in terrain_row.iter_mut() { + terrain_cell.x = x; + terrain_cell.y = y; + x += 1; + } + y += 1; + } + } + Ok(world) } fn visit_map(self, mut map: V) -> Result @@ -140,6 +164,7 @@ impl<'de> Deserialize<'de> for World { let mut terrain = None; let mut continent_offsets = None; let mut continent_widths = None; + let mut iteration = None; while let Some(key) = map.next_key()? { match key { @@ -179,6 +204,12 @@ impl<'de> Deserialize<'de> for World { } continent_widths = Some(map.next_value()?); }, + Field::Iteration => { + if iteration.is_some() { + return Err(Error::duplicate_field("iteration")); + } + iteration = Some(map.next_value()?); + }, } } @@ -229,7 +260,9 @@ impl<'de> Deserialize<'de> for World { attributes }); - Ok(World { + let iteration = iteration.ok_or_else(|| Error::missing_field("iteration"))?; + + let mut world = World { width, height, seed, @@ -245,7 +278,21 @@ impl<'de> Deserialize<'de> for World { min_temperature: world_attributes.min_temperature, rng: StdRng::seed_from_u64(seed as u64), - }) + iteration, + }; + { + let mut y = 0; + for terrain_row in world.terrain.iter_mut() { + let mut x = 0; + for terrain_cell in terrain_row.iter_mut() { + terrain_cell.x = x; + terrain_cell.y = y; + x += 1; + } + y += 1; + } + } + Ok(world) } } diff --git a/planet/src/world.rs b/planet/src/world.rs index 60e3dbb..f66518a 100644 --- a/planet/src/world.rs +++ b/planet/src/world.rs @@ -89,6 +89,7 @@ pub struct World { pub min_temperature: f32, #[serde(skip)] pub rng: StdRng, + pub iteration: usize, } #[derive(Debug, Clone, Default, Deserialize, Serialize)] @@ -98,24 +99,40 @@ pub struct TerrainCell { pub temperature: f32, #[serde(skip)] - pub x: usize, + pub x: usize, #[serde(skip)] - pub y: usize, + pub y: usize, + pub local_iteration: usize, pub biome_presences: Vec<(BiomeType, f32)>, } +impl TerrainCell { + pub fn get_next_local_random_int(&mut self, world: &World) -> f32 { + let seed = world.seed; + + let x = seed as f32 + self.x as f32; + let y = seed as f32 + self.y as f32; + let z = seed as f32 + world.iteration as f32 + (self.local_iteration - 1) as f32; + + drop(world); + self.local_iteration += 1; + + perlin::perlin_value(x, y, z) + } +} + impl World { pub const ALTITUDE_SPAN: f32 = World::MAX_ALTITUDE - World::MIN_ALTITUDE; - pub const CONTINENT_MAX_SIZE_FACTOR: f32 = 8.0; - pub const CONTINENT_MIN_SIZE_FACTOR: f32 = 6.0; + pub const CONTINENT_MAX_SIZE_FACTOR: f32 = 8.7; + pub const CONTINENT_MIN_SIZE_FACTOR: f32 = 5.7; pub const MAX_ALTITUDE: f32 = 15000.0; pub const MAX_RAINFALL: f32 = 13000.0; pub const MAX_TEMPERATURE: f32 = 30.0; pub const MIN_ALTITUDE: f32 = -15000.0; pub const MIN_RAINFALL: f32 = 0.0; pub const MIN_TEMPERATURE: f32 = -35.0; - pub const NUM_CONTINENTS: u8 = 6; + pub const NUM_CONTINENTS: u8 = 12; pub const RAINFALL_DRYNESS_FACTOR: f32 = 0.005; pub const RAINFALL_DRYNESS_OFFSET: f32 = World::RAINFALL_DRYNESS_FACTOR * World::MAX_RAINFALL; pub const RAINFALL_SPAN: f32 = World::MAX_RAINFALL - World::MIN_RAINFALL; @@ -127,10 +144,7 @@ impl World { width, height, seed, - terrain: vec![ - vec![TerrainCell::default(); width.try_into().unwrap()]; - height.try_into().unwrap() - ], + terrain: vec![vec![default(); width.try_into().unwrap()]; height.try_into().unwrap()], continent_offsets: [default(); World::NUM_CONTINENTS as usize], continent_sizes: [default(); World::NUM_CONTINENTS as usize], max_altitude: World::MIN_ALTITUDE, @@ -140,6 +154,7 @@ impl World { max_temperature: World::MIN_TEMPERATURE, min_temperature: World::MAX_TEMPERATURE, rng: StdRng::seed_from_u64(seed as u64), + iteration: 0, } } @@ -148,10 +163,7 @@ impl World { width, height, seed, - terrain: vec![ - vec![TerrainCell::default(); width.try_into().unwrap()]; - height.try_into().unwrap() - ], + terrain: vec![vec![default(); width.try_into().unwrap()]; height.try_into().unwrap()], continent_offsets: [default(); World::NUM_CONTINENTS as usize], continent_sizes: [default(); World::NUM_CONTINENTS as usize], max_altitude: World::MIN_ALTITUDE, @@ -161,6 +173,7 @@ impl World { max_temperature: World::MIN_TEMPERATURE, min_temperature: World::MAX_TEMPERATURE, rng: StdRng::seed_from_u64(seed as u64), + iteration: 0, } } @@ -187,35 +200,76 @@ impl World { Ok(()) } - fn generate_continents(&mut self) { - #[cfg(feature = "logging")] + fn generate_continents(&mut self, progress_sender: &Sender<(f32, String)>) { info!("Generating continents"); + + send_progress(progress_sender, 0.0, format!("Generating continents")); let width = self.width as f32; let height = self.height as f32; + const LONGTITUDE_FACTOR: f32 = 15.0; const LATITUDE_FACTOR: f32 = 6.0; - for i in 0..World::NUM_CONTINENTS { - // #[cfg(feature = "logging")] - // info!("Continents: {}/{}", i, World::NUM_CONTINENTS); - - self.continent_offsets[i as usize].x = self + let mut previous_position = Vec2 { + 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 / LATITUDE_FACTOR..height * (LATITUDE_FACTOR - 1.0) / LATITUDE_FACTOR, + .gen_range(0.0..width * (LONGTITUDE_FACTOR - 1.0) / LONGTITUDE_FACTOR), + y: self.rng.gen_range( + height / LATITUDE_FACTOR..height * (LATITUDE_FACTOR - 1.0) / LATITUDE_FACTOR, + ), + }; + for i in 0..World::NUM_CONTINENTS { + send_progress( + progress_sender, + i as f32 / World::NUM_CONTINENTS as f32, + format!("Generating continents: {i}/{{World::NUM_CONTINENTS}}"), + ); + let idx = i as usize; + + let width_offset = self.rng.gen_range(0.0..6.0); + + self.continent_offsets[idx] = previous_position; + + self.continent_sizes[idx] = Vec2 { + x: self.rng.gen_range( + World::CONTINENT_MIN_SIZE_FACTOR + width_offset + ..World::CONTINENT_MAX_SIZE_FACTOR + width_offset, + ), + y: self.rng.gen_range( + World::CONTINENT_MIN_SIZE_FACTOR + width_offset + ..World::CONTINENT_MAX_SIZE_FACTOR + width_offset, + ), + }; + + let y_position = self.rng.gen_range( + height / LATITUDE_FACTOR..height * (LATITUDE_FACTOR - 1.0) / LATITUDE_FACTOR, ); - self.continent_sizes[i as usize] = Vec2 { - x: self - .rng - .gen_range(World::CONTINENT_MIN_SIZE_FACTOR..World::CONTINENT_MAX_SIZE_FACTOR), - y: self - .rng - .gen_range(World::CONTINENT_MIN_SIZE_FACTOR..World::CONTINENT_MAX_SIZE_FACTOR) - / 2.0, + let new_vector = if i % 3 == 2 { + Vec2 { + x: f32::repeat( + previous_position.x + + self.rng.gen_range( + width * 4.0 / LONGTITUDE_FACTOR..width * 6.0 / LONGTITUDE_FACTOR, + ), + width, + ), + y: y_position, + } + } else { + Vec2 { + x: f32::repeat( + previous_position.x + + self.rng.gen_range( + width / LONGTITUDE_FACTOR..width * 2.0 / LONGTITUDE_FACTOR, + ), + width, + ), + y: y_position, + } }; + + previous_position = new_vector; } info!("Done generating continents"); } @@ -278,16 +332,16 @@ impl World { progress_sender: &Sender<(f32, String)>, ) -> Result<(), CartesianError> { info!("Generating altitude"); - self.generate_continents(); + self.generate_continents(progress_sender); - const RADIUS_1: f32 = 0.5; + const RADIUS_1: f32 = 0.75; const RADIUS_2: f32 = 8.0; const RADIUS_3: f32 = 4.0; const RADIUS_4: f32 = 8.0; const RADIUS_5: f32 = 16.0; const RADIUS_6: f32 = 64.0; const RADIUS_7: f32 = 128.0; - const RADIUS_8: f32 = 1.0; + const RADIUS_8: f32 = 1.5; const RADIUS_9: f32 = 1.0; let offset_1 = World::random_offset_vector(&mut self.rng); @@ -433,8 +487,10 @@ impl World { info!("Generating rainfall"); const RADIUS_1: f32 = 2.0; const RADIUS_2: f32 = 1.0; + const RADIUS_3: 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 height = self.terrain.len(); for y in 0..height { @@ -457,8 +513,12 @@ impl World { .random_noise_from_polar_coordinates(alpha, beta, RADIUS_2, offset_2)? * 1.5 + 0.25; + let random_noise_3 = + self.random_noise_from_polar_coordinates(alpha, beta, RADIUS_3, offset_3)?; - let latitude_factor = alpha + (random_noise_1 * 2.0 - 1.0) * PI * 0.2; + let value_a = mix_values(random_noise_1, random_noise_3, 0.15); + + let latitude_factor = alpha + (value_a * 2.0 - 1.0) * PI * 0.2; let latitude_modifier_1 = (1.5 * f32::sin(latitude_factor)) - 0.5; let latitude_modifier_2 = f32::cos(latitude_factor); @@ -545,8 +605,10 @@ impl World { progress_sender: &Sender<(f32, String)>, ) -> Result<(), CartesianError> { info!("Generating temperature"); - let offset = World::random_offset_vector(&mut self.rng); - const RADIUS: f32 = 2.0; + let offset_1 = World::random_offset_vector(&mut self.rng); + let offset_2 = World::random_offset_vector(&mut self.rng); + const RADIUS_1: f32 = 2.0; + const RADIUS_2: f32 = 16.0; let height = self.terrain.len(); for y in 0..height { @@ -564,12 +626,14 @@ impl World { let beta = (x as f32 / self.width as f32) * TAU; - let random_noise = - self.random_noise_from_polar_coordinates(alpha, beta, RADIUS, offset)?; + let random_noise_1 = + self.random_noise_from_polar_coordinates(alpha, beta, RADIUS_1, offset_1)?; + let random_noise_2 = + self.random_noise_from_polar_coordinates(alpha, beta, RADIUS_2, offset_2)?; let cell = &mut self.terrain[y][x]; - let latitude_modifer = mix_values(alpha, random_noise * PI, 0.1); + let latitude_modifer = alpha * 0.9 + (random_noise_1 + random_noise_2) * 0.05 * PI; let altitude_factor = f32::max( 0.0, (cell.altitude / World::MAX_ALTITUDE) * World::TEMPERATURE_ALTITUDE_FACTOR, diff --git a/planet/src/world_manager.rs b/planet/src/world_manager.rs index 40f4bd8..28f3921 100644 --- a/planet/src/world_manager.rs +++ b/planet/src/world_manager.rs @@ -42,7 +42,10 @@ 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"), + LoadError::InvalidSave(err) => f.write_fmt(format_args!( + "Loaded file is not a valid save - {}", + err.to_string() + )), } } } diff --git a/src/gui/windows/save_load.rs b/src/gui/windows/save_load.rs index 46d765b..feeb8d2 100644 --- a/src/gui/windows/save_load.rs +++ b/src/gui/windows/save_load.rs @@ -31,12 +31,12 @@ impl WindowSystem for SaveLoad<'_, '_> { if let Some(path) = tinyfiledialogs::save_file_dialog_with_filter( "Save world", state.file_name.as_str(), - &["*.rsplnt", "*.ron"], + &["*.rsplnt", "*.rsplnt"], "World file", ) { if let Err(err) = world_manager.save_world(&path) { // TODO: Error popup - error!("Failed to save: {err:#?}"); + error!("Failed to save: {err}"); } *state.file_name = path; } @@ -45,14 +45,15 @@ impl WindowSystem for SaveLoad<'_, '_> { if let Some(path) = tinyfiledialogs::open_file_dialog( "World file", state.file_name.as_str(), - Some((&["*.ron", "*.rsplnt"], "*.ron,*.rsplnt")), + Some((&["*.rsplnt"], "*.rspnt")), ) { if let Err(err) = world_manager.load_world(&path) { // TODO: Error popup - error!("Failed to load: {err:#?}"); + error!("Failed to load: {err}"); + } else { + should_redraw.0 = true; } *state.file_name = path; - should_redraw.0 = true; } } }); diff --git a/src/gui/windows/tile_info.rs b/src/gui/windows/tile_info.rs index 32a02aa..d9328c0 100644 --- a/src/gui/windows/tile_info.rs +++ b/src/gui/windows/tile_info.rs @@ -41,6 +41,7 @@ impl WindowSystem for TileInfo<'_, '_> { biome_presences, x, y, + .. } = &world.terrain[cursor_y as usize][cursor_x as usize]; _ = ui.label("Coordinates");