2022-09-18 17:30:04 +02:00
|
|
|
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");
|
2022-09-18 18:07:55 +02:00
|
|
|
const RADIUS: f32 = 2.0;
|
|
|
|
let offset = World::random_offset_vector(&mut self.rng);
|
2022-09-18 17:30:04 +02:00
|
|
|
|
|
|
|
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 width = self.terrain[y].len();
|
|
|
|
for x in 0..width {
|
2022-09-18 18:07:55 +02:00
|
|
|
let beta = (x as f32 / self.width as f32) * TAU;
|
|
|
|
|
|
|
|
let random_noise =
|
|
|
|
self.random_noise_from_polar_coordinates(alpha, beta, RADIUS, offset)?;
|
|
|
|
|
|
|
|
let latitude_factor = (alpha * 0.9) + (random_noise * 0.2 * PI) - 0.1;
|
|
|
|
let latitude_modifier_1 = (1.5 * f32::sin(latitude_factor)) - 0.5;
|
|
|
|
let latitude_modifier_2 = f32::cos(latitude_factor);
|
2022-09-18 17:30:04 +02:00
|
|
|
|
2022-09-18 18:07:55 +02:00
|
|
|
let offset_cell_x =
|
|
|
|
(width + x + f32::floor(latitude_modifier_2 * width as f32 / 20.0) as usize)
|
2022-09-18 17:30:04 +02:00
|
|
|
% width;
|
2022-09-18 18:07:55 +02:00
|
|
|
let offset_cell_2_x =
|
|
|
|
(width + x + f32::floor(latitude_modifier_2 * width as f32 / 10.0) as usize)
|
|
|
|
% width;
|
|
|
|
|
|
|
|
let offset_cell = &self.terrain[y][offset_cell_x];
|
|
|
|
let offset_altitude = f32::max(0.0, offset_cell.altitude);
|
|
|
|
|
|
|
|
let offset_cell_2 = &self.terrain[y][offset_cell_2_x];
|
|
|
|
let offset_altitude_2 = f32::max(0.0, offset_cell_2.altitude);
|
2022-09-18 17:30:04 +02:00
|
|
|
|
2022-09-18 18:07:55 +02:00
|
|
|
let cell = &mut self.terrain[y][x];
|
2022-09-18 17:30:04 +02:00
|
|
|
let altitude = f32::max(0.0, cell.altitude);
|
|
|
|
|
2022-09-18 18:07:55 +02:00
|
|
|
let altitude_modifier =
|
|
|
|
(altitude - (offset_altitude * 1.5) - (offset_altitude_2 * 1.5))
|
|
|
|
/ World::MAX_ALTITUDE;
|
2022-09-18 17:30:04 +02:00
|
|
|
|
2022-09-18 18:07:55 +02:00
|
|
|
let normalized_rainfall = mix_values(latitude_modifier_1, altitude_modifier, 0.6);
|
2022-09-18 17:30:04 +02:00
|
|
|
let rainfall = f32::min(
|
|
|
|
World::MAX_RAINFALL,
|
2022-09-18 18:07:55 +02:00
|
|
|
World::calculate_rainfall(normalized_rainfall),
|
2022-09-18 17:30:04 +02:00
|
|
|
);
|
|
|
|
|
|
|
|
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,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|