Tweak rainfall generation
cc4e5c393f1090b85f6be38d98a58e0899309c70
This commit is contained in:
parent
2c1697fae6
commit
99f4caab54
2 changed files with 102 additions and 24 deletions
|
@ -1,7 +1,7 @@
|
||||||
use std::{
|
use std::{
|
||||||
error::Error,
|
error::Error,
|
||||||
f32::consts::{PI, TAU},
|
f32::consts::{PI, TAU},
|
||||||
fmt::Display,
|
fmt::{Debug, Display},
|
||||||
};
|
};
|
||||||
|
|
||||||
use bevy::{math::Vec3A, prelude::Vec2, utils::default};
|
use bevy::{math::Vec3A, prelude::Vec2, utils::default};
|
||||||
|
@ -32,26 +32,50 @@ impl Error for WorldGenError {
|
||||||
impl Display for WorldGenError {
|
impl Display for WorldGenError {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
WorldGenError::CartesianError(err) => err.fmt(f),
|
WorldGenError::CartesianError(err) => Display::fmt(err, f),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Clone)]
|
||||||
pub struct World {
|
pub struct World {
|
||||||
pub width: i32,
|
pub width: i32,
|
||||||
pub height: i32,
|
pub height: i32,
|
||||||
pub seed: u32,
|
pub seed: u32,
|
||||||
|
|
||||||
pub terrain: Vec<Vec<TerrainCell>>,
|
pub terrain: Vec<Vec<TerrainCell>>,
|
||||||
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,
|
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, Copy, Clone, Default)]
|
#[derive(Debug, Copy, Clone, Default)]
|
||||||
pub struct TerrainCell {
|
pub struct TerrainCell {
|
||||||
pub altitude: f32,
|
pub altitude: f32,
|
||||||
pub rainfall: f32,
|
pub rainfall: f32,
|
||||||
|
pub rain_accumulated: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl World {
|
impl World {
|
||||||
|
@ -64,14 +88,16 @@ impl World {
|
||||||
vec![TerrainCell::default(); width.try_into().unwrap()];
|
vec![TerrainCell::default(); width.try_into().unwrap()];
|
||||||
height.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),
|
perlin: Perlin::new().set_seed(seed),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const NUM_CONTINENTS: u8 = 7;
|
pub const NUM_CONTINENTS: u8 = 7;
|
||||||
pub const CONTINENT_FACTOR: f32 = 0.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 MIN_ALTITUDE: f32 = -10000.0;
|
||||||
pub const MAX_ALTITUDE: f32 = 10000.0;
|
pub const MAX_ALTITUDE: f32 = 10000.0;
|
||||||
|
@ -93,9 +119,9 @@ impl World {
|
||||||
if let Err(err) = self.generate_altitude() {
|
if let Err(err) = self.generate_altitude() {
|
||||||
return Err(WorldGenError::CartesianError(err));
|
return Err(WorldGenError::CartesianError(err));
|
||||||
}
|
}
|
||||||
// if let Err(err) = self.generate_rainfall() {
|
if let Err(err) = self.generate_rainfall_alt() {
|
||||||
// return Err(WorldGenError::CartesianError(err));
|
return Err(WorldGenError::CartesianError(err));
|
||||||
// }
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -104,11 +130,16 @@ impl World {
|
||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::thread_rng();
|
||||||
let width = self.width as f32;
|
let width = self.width as f32;
|
||||||
let height = self.height as f32;
|
let height = self.height as f32;
|
||||||
for (idx, continent_offset) in self.contintent_offsets.iter_mut().enumerate() {
|
|
||||||
continent_offset.x = rng
|
for i in 0..Self::NUM_CONTINENTS {
|
||||||
.gen_range(width * idx as f32 * 2.0 / 5.0..width * (idx as f32 + 2.0) * 2.0 / 5.0)
|
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);
|
.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 mut max_value = 0.0;
|
||||||
let beta_factor = f32::sin(PI * y / height);
|
let beta_factor = f32::sin(PI * y / height);
|
||||||
|
|
||||||
for Vec2 {
|
for i in 0..Self::NUM_CONTINENTS {
|
||||||
|
let idx = i as usize;
|
||||||
|
let Vec2 {
|
||||||
x: continent_x,
|
x: continent_x,
|
||||||
y: contintent_y,
|
y: continent_y,
|
||||||
} in self.contintent_offsets
|
} = self.continent_offsets[idx];
|
||||||
{
|
|
||||||
let distance_x = f32::min(
|
let distance_x = f32::min(
|
||||||
f32::min((continent_x - x).abs(), (width + continent_x - x).abs()),
|
f32::min((continent_x - x).abs(), (width + continent_x - x).abs()),
|
||||||
(continent_x - x - width).abs(),
|
(continent_x - x - width).abs(),
|
||||||
) * beta_factor;
|
) * 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 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);
|
max_value = f32::max(max_value, value);
|
||||||
}
|
}
|
||||||
|
@ -246,6 +279,43 @@ impl World {
|
||||||
Self::MIN_ALTITUDE + (raw_altitude * Self::ALTITUDE_SPAN)
|
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> {
|
fn generate_rainfall(&mut self) -> Result<(), CartesianError> {
|
||||||
let offset = Self::random_offset_vector();
|
let offset = Self::random_offset_vector();
|
||||||
const RADIUS: f32 = 2.0;
|
const RADIUS: f32 = 2.0;
|
||||||
|
|
|
@ -27,7 +27,7 @@ impl WorldManager {
|
||||||
let altitude_color = Self::altitude_contour_color(cell.altitude);
|
let altitude_color = Self::altitude_contour_color(cell.altitude);
|
||||||
let rainfall_color = Self::rainfall_color(cell.rainfall);
|
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))
|
let r = (altitude_color.r() * (1.0 - normalized_rainfall))
|
||||||
+ (rainfall_color.r() * normalized_rainfall);
|
+ (rainfall_color.r() * normalized_rainfall);
|
||||||
|
@ -36,7 +36,7 @@ impl WorldManager {
|
||||||
let b = (altitude_color.b() * (1.0 - normalized_rainfall))
|
let b = (altitude_color.b() * (1.0 - normalized_rainfall))
|
||||||
+ (rainfall_color.b() * normalized_rainfall);
|
+ (rainfall_color.b() * normalized_rainfall);
|
||||||
|
|
||||||
Color::rgb(r, g, b)
|
Color::rgb_linear(r, g, b)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn altitude_color(altitude: f32) -> Color {
|
fn altitude_color(altitude: f32) -> Color {
|
||||||
|
@ -64,11 +64,19 @@ impl WorldManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn rainfall_color(rainfall: f32) -> Color {
|
fn rainfall_color(rainfall: f32) -> Color {
|
||||||
if rainfall < 0.0 {
|
if rainfall <= 0.0 {
|
||||||
Color::BLACK
|
Color::BLACK
|
||||||
} else {
|
} else {
|
||||||
let mult = rainfall / World::MAX_RAINFALL;
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue