Add biomes!

06f11e44aaeb99bc73522e86a08abd61a9da2e58
0e4387490e18cef0bccda5e7233d6949845e9620
759b4c6c744d4a7f44efb656ab8c5e66d178283c
af8b9e06e7935d17153c8603d2dcaf281de753c3
This commit is contained in:
Tobias Berger 2022-09-18 21:30:19 +02:00
parent 6a17e6df7b
commit fb78c618e4
Signed by: toby
GPG key ID: 2D05EFAB764D6A88
7 changed files with 635245 additions and 386343 deletions

1021157
planet.ron

File diff suppressed because it is too large Load diff

127
planet/src/biome.rs Normal file
View file

@ -0,0 +1,127 @@
use serde::{Deserialize, Serialize};
#[cfg(feature = "render")]
use bevy::render::color::Color;
use crate::World;
#[derive(Debug, Clone, Default)]
pub struct Biome {
pub name: String,
#[cfg(feature = "render")]
pub color: Color,
pub min_altitude: f32,
pub max_altitude: f32,
pub min_rainfall: f32,
pub max_rainfall: f32,
pub min_temperature: f32,
pub max_temperature: f32,
}
macro_rules! biome_enum {
($($Variant:ident),*$(,)?) =>
{
#[derive(Debug, Copy, Clone, Deserialize, Serialize)]
pub enum BiomeType {
$($Variant),*,
}
impl BiomeType {
pub const BIOMES: &'static [BiomeType] = &[$(BiomeType::$Variant),*];
}
}
}
biome_enum!(IceCap, Ocean, Grassland, Forest, Taiga, Tundra, Desert, Rainforest);
impl From<BiomeType> for Biome {
fn from(biome_type: BiomeType) -> Biome {
match biome_type {
BiomeType::IceCap => Biome {
name: "Ice Cap".into(),
color: Color::rgb_u8(253, 244, 235),
min_altitude: World::MIN_ALTITUDE,
max_altitude: World::MAX_ALTITUDE,
min_rainfall: World::MIN_RAINFALL,
max_rainfall: World::MAX_RAINFALL,
min_temperature: World::MIN_TEMPERATURE,
max_temperature: -15.0,
},
BiomeType::Ocean => Biome {
name: "Ocean".into(),
color: Color::rgb_u8(28, 66, 84),
min_altitude: World::MIN_ALTITUDE,
max_altitude: 0.0,
min_rainfall: World::MIN_RAINFALL,
max_rainfall: World::MAX_RAINFALL,
min_temperature: -15.0,
max_temperature: World::MAX_TEMPERATURE,
},
BiomeType::Grassland => Biome {
name: "Grassland".into(),
color: Color::rgb_u8(167, 177, 84),
min_altitude: 0.0,
max_altitude: World::MAX_ALTITUDE,
min_rainfall: 25.0,
max_rainfall: 1475.0,
min_temperature: -5.0,
max_temperature: World::MAX_TEMPERATURE,
},
BiomeType::Forest => Biome {
name: "Forest".into(),
color: Color::rgb_u8(76, 132, 55),
min_altitude: 0.0,
max_altitude: World::MAX_ALTITUDE,
min_rainfall: 975.0,
max_rainfall: 2475.0,
min_temperature: -5.0,
max_temperature: World::MAX_TEMPERATURE,
},
BiomeType::Taiga => Biome {
name: "Taiga".into(),
color: Color::rgb_u8(43, 63, 40),
min_altitude: 0.0,
max_altitude: World::MAX_ALTITUDE,
min_rainfall: 475.0,
max_rainfall: World::MAX_RAINFALL,
min_temperature: -15.0,
max_temperature: -0.0,
},
BiomeType::Tundra => Biome {
name: "Tundra ".into(),
color: Color::rgb_u8(139, 139, 128),
min_altitude: 0.0,
max_altitude: World::MAX_ALTITUDE,
min_rainfall: World::MIN_RAINFALL,
max_rainfall: 725.0,
min_temperature: -20.0,
max_temperature: -0.0,
},
BiomeType::Desert => Biome {
name: "Desert ".into(),
color: Color::rgb_u8(253, 225, 171),
min_altitude: 0.0,
max_altitude: World::MAX_ALTITUDE,
min_rainfall: World::MIN_RAINFALL,
max_rainfall: 125.0,
min_temperature: -5.0,
max_temperature: World::MAX_TEMPERATURE,
},
BiomeType::Rainforest => Biome {
name: "Rainforest".into(),
color: Color::rgb_u8(59, 103, 43),
min_altitude: 0.0,
max_altitude: World::MAX_ALTITUDE,
min_rainfall: 1975.0,
max_rainfall: World::MAX_RAINFALL,
min_temperature: -5.0,
max_temperature: World::MAX_TEMPERATURE,
},
}
}
}
impl From<&BiomeType> for Biome {
fn from(biome_type: &BiomeType) -> Biome {
(*biome_type).into()
}
}

View file

@ -34,6 +34,8 @@
pub mod world;
pub use world::*;
pub mod biome;
pub use biome::Biome;
pub mod world_manager;
pub use world_manager::*;
pub mod math_util;

View file

@ -7,7 +7,7 @@ use std::{
// TODO: Logging doesn't seem to work here? Figure out why and fix
use crate::perlin;
use crate::{biome::BiomeType, perlin, Biome};
use bevy::{log::info, math::Vec3A, prelude::Vec2, utils::default};
use rand::{rngs::StdRng, Rng, SeedableRng};
@ -65,23 +65,13 @@ pub struct World {
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,
pub biome_presences: Vec<(BiomeType, f32)>,
}
impl World {
@ -106,33 +96,32 @@ impl World {
}
}
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;
const NUM_CONTINENTS: u8 = 7;
const CONTINENT_MIN_WIDTH_FACTOR: f32 = 3.0;
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(crate) const MIN_ALTITUDE: f32 = -15000.0;
pub(crate) const MAX_ALTITUDE: f32 = 15000.0;
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;
const MOUNTAIN_RANGE_MIX_FACTOR: f32 = 0.075;
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;
const TERRAIN_NOISE_FACTOR_1: f32 = 0.15;
const TERRAIN_NOISE_FACTOR_2: f32 = 0.15;
const TERRAIN_NOISE_FACTOR_3: f32 = 0.1;
const TERRAIN_NOISE_FACTOR_4: f32 = 2.5;
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(crate) const MIN_RAINFALL: f32 = 0.0;
pub(crate) const MAX_RAINFALL: f32 = 7500.0;
const RAINFALL_SPAN: f32 = World::MAX_RAINFALL - World::MIN_RAINFALL;
const RAINFALL_DRYNESS_FACTOR: f32 = 0.005;
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(crate) const MIN_TEMPERATURE: f32 = -60.0;
pub(crate) const MAX_TEMPERATURE: f32 = 30.0;
const TEMPERATURE_SPAN: f32 = World::MAX_TEMPERATURE - World::MIN_TEMPERATURE;
const TEMPERATURE_ALTITUDE_FACTOR: f32 = 1.0;
pub fn generate(&mut self) -> Result<(), WorldGenError> {
if let Err(err) = self.generate_altitude() {
@ -145,6 +134,8 @@ impl World {
return Err(WorldGenError::CartesianError(err));
}
self.generate_biomes();
Ok(())
}
@ -254,9 +245,16 @@ impl World {
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);
World::TERRAIN_NOISE_FACTOR_1 * World::TERRAIN_NOISE_FACTOR_4,
) * mix_values(
1.0,
value_4,
World::TERRAIN_NOISE_FACTOR_2 * World::TERRAIN_NOISE_FACTOR_4,
) * mix_values(
1.0,
value_5,
World::TERRAIN_NOISE_FACTOR_3 * World::TERRAIN_NOISE_FACTOR_4,
);
let mut value_d = mix_values(value_a, value_c, 0.25);
value_d = mix_values(value_d, value_c, 0.1);
@ -364,10 +362,10 @@ impl World {
(altitude - (offset_altitude * 1.5) - (offset_altitude_2 * 1.5))
/ World::MAX_ALTITUDE;
let normalized_rainfall = mix_values(latitude_modifier_1, altitude_modifier, 0.6);
let rainfall_value = mix_values(latitude_modifier_1, altitude_modifier, 0.63);
let rainfall = f32::min(
World::MAX_RAINFALL,
World::calculate_rainfall(normalized_rainfall),
World::calculate_rainfall(rainfall_value),
);
cell.rainfall = rainfall;
@ -408,17 +406,14 @@ impl World {
let cell = &mut self.terrain[y][x];
let latitude_modifer = mix_values(alpha, random_noise * PI, 0.1);
let altitude_factor = f32::max(
0.0,
(cell.altitude / World::MAX_ALTITUDE)
* 2.5
* World::TEMPERATURE_ALTITUDE_FACTOR,
(cell.altitude / World::MAX_ALTITUDE) * World::TEMPERATURE_ALTITUDE_FACTOR,
);
let temperature =
World::calculate_temperature(f32::sin(latitude_modifer) - 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 {
@ -440,4 +435,83 @@ impl World {
World::MAX_TEMPERATURE,
)
}
fn generate_biomes(&mut self) {
for y in 0..self.terrain.len() {
for x in 0..self.terrain[y].len() {
let cell = &self.terrain[y][x];
let mut total_presence = 0.0;
let mut biome_presences = vec![];
for biome_type in BiomeType::BIOMES {
let presence = self.biome_presence(cell, &biome_type.into());
if presence <= 0.0 {
continue;
}
biome_presences.push((*biome_type, presence));
total_presence += presence;
}
self.terrain[y][x].biome_presences = biome_presences
.iter()
.map(|(biome_type, presence)| (*biome_type, presence / total_presence))
.collect();
}
}
}
fn biome_presence(&self, cell: &TerrainCell, biome: &Biome) -> f32 {
let mut presence = 0.0;
let altitude_diff = cell.altitude - biome.min_altitude;
if altitude_diff < 0.0 {
return 0.0;
}
let altitude_factor = altitude_diff / (biome.max_altitude - biome.min_altitude);
if altitude_factor > 1.0 {
return 0.0;
};
presence += if altitude_factor > 0.5 {
1.0 - altitude_factor
} else {
altitude_factor
};
let rainfall_diff = cell.rainfall - biome.min_rainfall;
if rainfall_diff < 0.0 {
return 0.0;
}
let rainfall_factor = rainfall_diff / (biome.max_rainfall - biome.min_rainfall);
if rainfall_factor > 1.0 {
return 0.0;
}
presence += if rainfall_factor > 0.5 {
1.0 - rainfall_factor
} else {
rainfall_factor
};
let temperature_diff = cell.temperature - biome.min_temperature;
if temperature_diff < 0.0 {
return 0.0;
}
let temperature_factor = temperature_diff / (biome.max_temperature - biome.min_temperature);
if temperature_factor > 1.0 {
return 0.0;
}
presence += if temperature_factor > 0.5 {
1.0 - temperature_factor
} else {
temperature_factor
};
presence
}
}

View file

@ -1,6 +1,6 @@
#[cfg(feature = "render")]
use crate::TerrainCell;
use crate::{World, WorldGenError};
use crate::{Biome, World, WorldGenError};
#[cfg(all(feature = "debug", feature = "render"))]
use bevy::log::debug;
use bevy::log::warn;
@ -94,6 +94,8 @@ pub struct WorldManager {
#[cfg(feature = "render")]
temperature_visible: bool,
#[cfg(feature = "render")]
biomes_visible: bool,
#[cfg(feature = "render")]
contours: bool,
}
@ -108,6 +110,8 @@ impl WorldManager {
#[cfg(feature = "render")]
temperature_visible: false,
#[cfg(feature = "render")]
biomes_visible: false,
#[cfg(feature = "render")]
contours: true,
}
}
@ -208,6 +212,17 @@ impl WorldManager {
self.temperature_visible = !self.temperature_visible;
}
#[cfg(feature = "render")]
pub fn toggle_biomes(&mut self) {
#[cfg(feature = "debug")]
if self.temperature_visible {
debug!("Turning biomes off");
} else {
debug!("Turning biomes on");
}
self.biomes_visible = !self.biomes_visible;
}
#[cfg(feature = "render")]
pub fn toggle_contours(&mut self) {
#[cfg(feature = "debug")]
@ -237,6 +252,10 @@ impl WorldManager {
#[cfg(feature = "render")]
fn generate_color(&self, cell: &TerrainCell) -> Color {
if self.biomes_visible {
return WorldManager::biome_color(cell);
}
let altitude_color = if self.contours {
WorldManager::altitude_contour_color(cell.altitude)
} else {
@ -245,9 +264,9 @@ impl WorldManager {
let mut layer_count = 1.0;
let mut r = altitude_color.r();
let mut g = altitude_color.g();
let mut b = altitude_color.b();
let mut red = altitude_color.r();
let mut green = altitude_color.g();
let mut blue = altitude_color.b();
if self.rainfall_visible {
layer_count += 1.0;
@ -258,9 +277,9 @@ impl WorldManager {
// WorldManager::rainfall_color(cell.rainfall)
// };
r += rainfall_color.r();
g += rainfall_color.g();
b += rainfall_color.b();
red += rainfall_color.r();
green += rainfall_color.g();
blue += rainfall_color.b();
}
if self.temperature_visible {
@ -272,12 +291,12 @@ impl WorldManager {
// WorldManager::temperature_color(cell.temperature)
// };
r += temperature_color.r();
g += temperature_color.g();
b += temperature_color.b();
red += temperature_color.r();
green += temperature_color.g();
blue += temperature_color.b();
}
Color::rgb(r / layer_count, g / layer_count, b / layer_count)
Color::rgb(red / layer_count, green / layer_count, blue / layer_count)
}
#[cfg(feature = "render")]
@ -318,15 +337,6 @@ impl WorldManager {
Color::rgb(0.0, shade_value, 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;
@ -341,14 +351,19 @@ impl WorldManager {
}
#[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)
}
fn biome_color(cell: &TerrainCell) -> Color {
cell.biome_presences
.iter()
.fold(Color::BLACK, |color, (biome_type, presence)| {
let biome: Biome = (*biome_type).into();
let biome_color = biome.color;
#[cfg(feature = "render")]
fn normalize_temperature(temperature: f32) -> f32 {
(temperature - World::MIN_TEMPERATURE) / World::TEMPERATURE_SPAN
Color::rgb(
color.r() + (biome_color.r() * presence),
color.g() + (biome_color.g() * presence),
color.b() + (biome_color.b() * presence),
)
})
}
#[cfg(feature = "render")]

View file

@ -2,28 +2,29 @@
use bevy::ecs::component::Component;
#[cfg(feature = "render")]
macro_rules! define_enum {
($Name:ident { $($Variant:ident),* $(,)* }) =>
macro_rules! toolbar_enum {
($($Variant:ident),*$(,)?) =>
{
#[derive(Debug, Component, Copy, Clone)]
pub enum $Name {
pub enum ToolbarButton {
$($Variant),*,
}
impl $Name {
pub const ITEMS: &'static [$Name] = &[$($Name::$Variant),*];
impl ToolbarButton {
pub const BUTTONS: &'static [ToolbarButton] = &[$(ToolbarButton::$Variant),*];
}
}
}
#[cfg(feature = "render")]
define_enum!(ToolbarButton {
toolbar_enum!(
GenerateWorld,
SaveWorld,
LoadWorld,
Rainfall,
Temperature,
Biomes,
Contours,
});
);
#[cfg(feature = "render")]
impl From<ToolbarButton> for &'static str {
@ -32,6 +33,7 @@ impl From<ToolbarButton> for &'static str {
ToolbarButton::Rainfall => "Toggle rainfall",
ToolbarButton::Temperature => "Toggle temperature",
ToolbarButton::Contours => "Toggle contours",
ToolbarButton::Biomes => "Toggle biomes",
ToolbarButton::GenerateWorld => "Generate new world",
ToolbarButton::SaveWorld => "Save",
ToolbarButton::LoadWorld => "Load",
@ -41,14 +43,7 @@ impl From<ToolbarButton> for &'static str {
#[cfg(feature = "render")]
impl From<&ToolbarButton> for &'static str {
fn from(button: &ToolbarButton) -> Self {
match button {
ToolbarButton::Rainfall => "Toggle rainfall",
ToolbarButton::Temperature => "Toggle temperature",
ToolbarButton::Contours => "Toggle contours",
ToolbarButton::GenerateWorld => "Generate new world",
ToolbarButton::SaveWorld => "Save",
ToolbarButton::LoadWorld => "Load",
}
(*button).into()
}
}

View file

@ -82,6 +82,7 @@ use bevy::{
window::{CursorIcon, WindowDescriptor, Windows},
winit::WinitSettings,
};
use planet::Biome;
#[cfg(all(feature = "debug", feature = "render"))]
use bevy::{
@ -154,6 +155,12 @@ fn handle_toolbar_button(
world_manager.toggle_temperature();
refresh_world_texture(&mut images, &world_manager);
}
ToolbarButton::Biomes => {
#[cfg(feature = "debug")]
debug!("Toggling biomes");
world_manager.toggle_biomes();
refresh_world_texture(&mut images, &world_manager);
}
ToolbarButton::Contours => {
#[cfg(feature = "debug")]
debug!("Toggling contours");
@ -250,7 +257,7 @@ fn update_info_panel(
#[cfg(feature = "debug")]
{
format!(
"FPS: ~{}\nMouse position: {}\nAltitude: {}\nRainfall: {}\nTemperature: {}",
"FPS: ~{}\nMouse position: {}\nAltitude: {}\nRainfall: {}\nTemperature: {}\n\n{}",
match diagnostics.get_measurement(FrameTimeDiagnosticsPlugin::FPS) {
None => f64::NAN,
Some(fps) => fps.value.round(),
@ -258,15 +265,40 @@ fn update_info_panel(
*cursor_position,
cell.altitude,
cell.rainfall,
cell.temperature
cell.temperature,
cell.biome_presences
.iter()
.map(|(biome_type, presence)| {
format!(
"Biome: {} ({:.2}%)",
(<Biome>::from(biome_type).name),
presence * 100.0
)
})
.collect::<Vec<String>>()
.join("\n")
)
}
#[cfg(not(feature = "debug"))]
{
format!(
"Mouse position: {}\nAltitude: {}\nRainfall: {}\nTemperature: {}",
*cursor_position, cell.altitude, cell.rainfall, cell.temperature
"Mouse position: {}\nAltitude: {}\nRainfall: {}\nTemperature: {}\n{}",
*cursor_position,
cell.altitude,
cell.rainfall,
cell.temperature,
cell.biome_presences
.iter()
.map(|(biome_type, presence)| {
format!(
"Biome: {} ({:.2}%)",
(<Biome>::from(biome_type).name),
presence * 100.0
)
})
.collect::<Vec<String>>()
.join("\n")
)
}
} else {
@ -422,7 +454,7 @@ fn generate_graphics(
..default()
})
.with_children(|button_box| {
ToolbarButton::ITEMS.iter().for_each(|&button_type| {
ToolbarButton::BUTTONS.iter().for_each(|&button_type| {
_ = button_box
.spawn_bundle(toolbar_button())
.with_children(|button| {