diff --git a/save/src/world.rs b/save/src/world.rs index fc058d6..4fd211d 100644 --- a/save/src/world.rs +++ b/save/src/world.rs @@ -1,7 +1,7 @@ use std::{ error::Error, f32::consts::{PI, TAU}, - fmt::Display, + fmt::{Debug, Display}, }; use bevy::{math::Vec3A, prelude::Vec2, utils::default}; @@ -32,26 +32,50 @@ impl Error for WorldGenError { impl Display for WorldGenError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - WorldGenError::CartesianError(err) => err.fmt(f), + WorldGenError::CartesianError(err) => Display::fmt(err, f), } } } -#[derive(Debug, Clone)] +#[derive(Clone)] pub struct World { pub width: i32, pub height: i32, pub seed: u32, pub terrain: Vec>, - contintent_offsets: [Vec2; World::NUM_CONTINENTS as usize], + continent_offsets: [Vec2; World::NUM_CONTINENTS as usize], + continent_widths: [f32; World::NUM_CONTINENTS as usize], 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::() + / (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, Copy, Clone, Default)] pub struct TerrainCell { pub altitude: f32, pub rainfall: f32, + pub rain_accumulated: f32, } impl World { @@ -64,14 +88,16 @@ impl World { vec![TerrainCell::default(); width.try_into().unwrap()]; height.try_into().unwrap() ], - contintent_offsets: [default(); Self::NUM_CONTINENTS as usize], + 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_WIDTH_FACTOR: f32 = 5.0; + 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; @@ -93,9 +119,9 @@ impl World { 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_rainfall_alt() { + return Err(WorldGenError::CartesianError(err)); + } Ok(()) } @@ -104,11 +130,16 @@ impl World { let mut rng = rand::thread_rng(); let width = self.width as f32; let height = self.height as f32; - for (idx, continent_offset) in self.contintent_offsets.iter_mut().enumerate() { - continent_offset.x = rng - .gen_range(width * idx as f32 * 2.0 / 5.0..width * (idx as f32 + 2.0) * 2.0 / 5.0) + + for i in 0..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); - continent_offset.y = rng.gen_range(height * 1.0 / 6.0..height * 5.0 / 6.0); + 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); } } @@ -121,21 +152,23 @@ impl World { let mut max_value = 0.0; let beta_factor = f32::sin(PI * y / height); - for Vec2 { - x: continent_x, - y: contintent_y, - } in self.contintent_offsets - { + 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(contintent_y - y); + 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_WIDTH_FACTOR * distance / width); + let value = f32::max(0.0, 1.0 - self.continent_widths[idx] * distance / width); max_value = f32::max(max_value, value); } @@ -246,6 +279,43 @@ impl World { Self::MIN_ALTITUDE + (raw_altitude * Self::ALTITUDE_SPAN) } + fn generate_rainfall_alt(&mut self) -> Result<(), CartesianError> { + const MAX_CYCLES: u8 = 25; + + const ACCUMULATED_RAIN_FACTOR: f32 = 2.0; + const RAINFALL_FACTOR: f32 = 0.1; + + for _ in 0..MAX_CYCLES { + for x in 0..self.width { + let prev_x = (x + 1) % self.width; + + for y in 0..self.height { + let width_factor = f32::sin(PI * y as f32 / self.height as f32); + + let mut cell = self.terrain[y as usize][x as usize]; + cell.rain_accumulated = 0.0; + + if cell.altitude <= 0.0 { + cell.rain_accumulated += + width_factor * ACCUMULATED_RAIN_FACTOR * Self::MAX_RAINFALL; + } + + let prev_cell = self.terrain[y as usize][prev_x as usize]; + + cell.rain_accumulated += prev_cell.rain_accumulated; + cell.rainfall += cell.rain_accumulated * RAINFALL_FACTOR; + cell.rain_accumulated -= cell.rainfall; + + 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; diff --git a/save/src/world_manager.rs b/save/src/world_manager.rs index 62b8bd6..0c9137f 100644 --- a/save/src/world_manager.rs +++ b/save/src/world_manager.rs @@ -27,7 +27,7 @@ impl WorldManager { let altitude_color = Self::altitude_contour_color(cell.altitude); let rainfall_color = Self::rainfall_color(cell.rainfall); - let normalized_rainfall = f32::max(cell.rainfall / World::MAX_RAINFALL, 0.0); + let normalized_rainfall = Self::normalize_rainfall(cell.rainfall); let r = (altitude_color.r() * (1.0 - normalized_rainfall)) + (rainfall_color.r() * normalized_rainfall); @@ -36,7 +36,7 @@ impl WorldManager { let b = (altitude_color.b() * (1.0 - normalized_rainfall)) + (rainfall_color.b() * normalized_rainfall); - Color::rgb(r, g, b) + Color::rgb_linear(r, g, b) } fn altitude_color(altitude: f32) -> Color { @@ -64,11 +64,19 @@ impl WorldManager { } fn rainfall_color(rainfall: f32) -> Color { - if rainfall < 0.0 { + if rainfall <= 0.0 { Color::BLACK } else { let mult = rainfall / World::MAX_RAINFALL; - Color::GREEN * mult + Color::rgb(0.0, mult, 0.0) + } + } + + fn normalize_rainfall(rainfall: f32) -> f32 { + if rainfall <= 0.0 { + rainfall + } else { + rainfall / World::MAX_ALTITUDE } }