Add temperature

cfe7401c7aeedec7faea59e8b6dfa0075545b484
This commit is contained in:
Tobias Berger 2022-09-06 12:08:05 +02:00
parent 2c85d95b7a
commit 92f25cf159
Signed by: toby
GPG key ID: 2D05EFAB764D6A88
4 changed files with 225 additions and 60 deletions

View file

@ -6,7 +6,7 @@
#![warn(macro_use_extern_crate)] #![warn(macro_use_extern_crate)]
#![warn(meta_variable_misuse)] #![warn(meta_variable_misuse)]
#![warn(missing_abi)] #![warn(missing_abi)]
#![warn(missing_copy_implementations)] // #![warn(missing_copy_implementations)]
#![warn(missing_debug_implementations)] #![warn(missing_debug_implementations)]
// #![warn(missing_docs)] // #![warn(missing_docs)]
#![warn(non_ascii_idents)] #![warn(non_ascii_idents)]

View file

@ -71,17 +71,19 @@ impl Debug for World {
} }
} }
#[derive(Debug, Copy, Clone, Default)] #[derive(Debug, Clone, Default)]
pub struct Biome { pub struct Biome {
pub altitude: f32, pub altitude: f32,
pub rainfall: f32, pub rainfall: f32,
pub temperature: f32, pub temperature: f32,
} }
#[derive(Debug, Copy, Clone, Default)] #[derive(Debug, Clone, Default)]
pub struct TerrainCell { pub struct TerrainCell {
pub altitude: f32, pub altitude: f32,
pub rainfall: f32, pub rainfall: f32,
pub temperature: f32,
pub rain_accumulated: f32, pub rain_accumulated: f32,
pub previous_rain_accumulated: f32, pub previous_rain_accumulated: f32,
} }
@ -118,11 +120,16 @@ impl World {
pub const TERRAIN_NOISE_FACTOR_2: f32 = 0.15; pub const TERRAIN_NOISE_FACTOR_2: f32 = 0.15;
pub const TERRAIN_NOISE_FACTOR_3: f32 = 0.1; pub const TERRAIN_NOISE_FACTOR_3: f32 = 0.1;
pub const MIN_RAINFALL: f32 = -10.0; pub const MIN_RAINFALL: f32 = -20.0;
pub const MAX_RAINFALL: f32 = 100.0; pub const MAX_RAINFALL: f32 = 100.0;
pub const RAINFALL_SPAN: f32 = Self::MAX_RAINFALL - Self::MIN_RAINFALL; pub const RAINFALL_SPAN: f32 = Self::MAX_RAINFALL - Self::MIN_RAINFALL;
pub const RAINFALL_ALTITUDE_FACTOR: f32 = 1.0; pub const RAINFALL_ALTITUDE_FACTOR: f32 = 1.0;
pub const MIN_TEMPERATURE: f32 = -100.0;
pub const MAX_TEMPERATURE: f32 = 100.0;
pub const TEMPERATURE_SPAN: f32 = Self::MAX_TEMPERATURE - Self::MIN_RAINFALL;
pub const TEMPERATURE_ALTITUDE_FACTOR: f32 = 1.0;
pub fn generate(&mut self) -> Result<(), WorldGenError> { pub fn generate(&mut self) -> Result<(), WorldGenError> {
if let Err(err) = self.generate_altitude() { if let Err(err) = self.generate_altitude() {
return Err(WorldGenError::CartesianError(err)); return Err(WorldGenError::CartesianError(err));
@ -130,6 +137,9 @@ impl World {
if let Err(err) = self.generate_rainfall() { if let Err(err) = self.generate_rainfall() {
return Err(WorldGenError::CartesianError(err)); return Err(WorldGenError::CartesianError(err));
} }
if let Err(err) = self.generate_temperature() {
return Err(WorldGenError::CartesianError(err));
}
Ok(()) Ok(())
} }
@ -357,12 +367,12 @@ impl World {
for x in 0..self.terrain[y].len() { for x in 0..self.terrain[y].len() {
let beta = (x as f32 / self.width as f32) * TAU; let beta = (x as f32 / self.width as f32) * TAU;
let value = let random_noise =
self.random_noise_from_polar_coordinates(alpha, beta, RADIUS, offset)?; self.random_noise_from_polar_coordinates(alpha, beta, RADIUS, offset)?;
let mut cell = &mut self.terrain[y][x]; let mut cell = &mut self.terrain[y][x];
let base_rainfall = Self::calculate_rainfall(value); let base_rainfall = Self::calculate_rainfall(random_noise);
let altitude_factor = f32::clamp( let altitude_factor = f32::clamp(
(cell.altitude / Self::MAX_ALTITUDE) * Self::RAINFALL_ALTITUDE_FACTOR, (cell.altitude / Self::MAX_ALTITUDE) * Self::RAINFALL_ALTITUDE_FACTOR,
0.0, 0.0,
@ -383,4 +393,43 @@ impl World {
Self::MAX_RAINFALL, Self::MAX_RAINFALL,
) )
} }
fn generate_temperature(&mut self) -> Result<(), CartesianError> {
let offset = Self::random_offset_vector();
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 = 1.0
- f32::clamp(
(cell.altitude / Self::MAX_ALTITUDE) * Self::TEMPERATURE_ALTITUDE_FACTOR,
0.0,
1.0,
);
let latitude_modifer = (alpha * 0.8) + (random_noise * 0.2 * PI);
let base_temperature = Self::calculate_temperature(f32::sin(latitude_modifer));
cell.temperature = base_temperature * altitude_factor;
}
}
Ok(())
}
fn calculate_temperature(raw_temperature: f32) -> f32 {
f32::clamp(
(raw_temperature * Self::TEMPERATURE_SPAN) + Self::MIN_TEMPERATURE,
0.0,
Self::MAX_TEMPERATURE,
)
}
} }

View file

@ -16,6 +16,8 @@ pub struct WorldManager {
world: Option<World>, world: Option<World>,
#[cfg(feature = "render")] #[cfg(feature = "render")]
rainfall_visible: bool, rainfall_visible: bool,
#[cfg(feature = "render")]
temperature_visible: bool,
} }
impl WorldManager { impl WorldManager {
@ -25,6 +27,7 @@ impl WorldManager {
image_handle_id: HandleId::default::<Image>(), image_handle_id: HandleId::default::<Image>(),
world: None, world: None,
rainfall_visible: false, rainfall_visible: false,
temperature_visible: false,
} }
} }
@ -34,11 +37,20 @@ impl WorldManager {
debug!("Turning rainfall off"); debug!("Turning rainfall off");
} else { } else {
debug!("Turning rainfall on"); debug!("Turning rainfall on");
debug!("World: {:#?}", self.world);
} }
self.rainfall_visible = !self.rainfall_visible; self.rainfall_visible = !self.rainfall_visible;
} }
#[cfg(feature = "render")]
pub fn toggle_temperature(&mut self) {
if self.temperature_visible {
debug!("Turning temperature off");
} else {
debug!("Turning temperature on");
}
self.temperature_visible = !self.temperature_visible;
}
pub fn get_world(&self) -> Option<&World> { pub fn get_world(&self) -> Option<&World> {
self.world.as_ref() self.world.as_ref()
} }
@ -52,24 +64,46 @@ impl WorldManager {
} }
#[cfg(feature = "render")] #[cfg(feature = "render")]
fn generate_color(cell: &TerrainCell, show_rainfall: bool) -> Color { fn generate_color(&self, cell: &TerrainCell) -> Color {
let altitude_color = Self::altitude_contour_color(cell.altitude); let mut final_color = Self::altitude_color(cell.altitude);
let rainfall_color = if show_rainfall {
Self::rainfall_color(cell.rainfall)
} else {
Color::BLACK
};
if self.rainfall_visible {
let rainfall_color = Self::rainfall_color(cell.rainfall);
let normalized_rainfall = Self::normalize_rainfall(cell.rainfall); let normalized_rainfall = Self::normalize_rainfall(cell.rainfall);
let r = (altitude_color.r() * (1.0 - normalized_rainfall)) _ = final_color.set_r(
+ (rainfall_color.r() * normalized_rainfall); (final_color.r() * (1.0 - normalized_rainfall))
let g = (altitude_color.g() * (1.0 - normalized_rainfall)) + (rainfall_color.r() * normalized_rainfall),
+ (rainfall_color.g() * normalized_rainfall); );
let b = (altitude_color.b() * (1.0 - normalized_rainfall)) _ = final_color.set_g(
+ (rainfall_color.b() * normalized_rainfall); (final_color.g() * (1.0 - normalized_rainfall))
+ (rainfall_color.g() * normalized_rainfall),
);
_ = final_color.set_b(
(final_color.b() * (1.0 - normalized_rainfall))
+ (rainfall_color.b() * normalized_rainfall),
);
}
Color::rgb_linear(r, g, b) if self.temperature_visible {
let temperature_color = Self::temperature_color(cell.temperature);
let normalized_temperature = Self::normalize_temperature(cell.temperature);
_ = final_color.set_r(
(final_color.r() * (1.0 - normalized_temperature))
+ (temperature_color.r() * normalized_temperature),
);
_ = final_color.set_g(
(final_color.g() * (1.0 - normalized_temperature))
+ (temperature_color.g() * normalized_temperature),
);
_ = final_color.set_b(
(final_color.b() * (1.0 - normalized_temperature))
+ (temperature_color.b() * normalized_temperature),
);
}
final_color
} }
/* /*
@ -114,6 +148,17 @@ impl WorldManager {
} }
} }
#[cfg(feature = "render")]
fn temperature_color(temperature: f32) -> Color {
let normalized_temperature = Self::normalize_temperature(temperature);
Color::rgb(normalized_temperature, 1.0 - normalized_temperature, 0.0)
}
#[cfg(feature = "render")]
fn normalize_temperature(temperature: f32) -> f32 {
(temperature - World::MIN_TEMPERATURE) / World::TEMPERATURE_SPAN
}
#[cfg(feature = "render")] #[cfg(feature = "render")]
pub fn world_colors(&self) -> Vec<Color> { pub fn world_colors(&self) -> Vec<Color> {
match self.get_world() { match self.get_world() {
@ -123,7 +168,7 @@ impl WorldManager {
terrain_cells terrain_cells
.iter() .iter()
.map(|cell| Self::generate_color(cell, self.rainfall_visible)) .map(|cell| self.generate_color(cell))
.collect() .collect()
} }
} }

View file

@ -6,7 +6,7 @@
#![warn(macro_use_extern_crate)] #![warn(macro_use_extern_crate)]
#![warn(meta_variable_misuse)] #![warn(meta_variable_misuse)]
#![warn(missing_abi)] #![warn(missing_abi)]
#![warn(missing_copy_implementations)] // #![warn(missing_copy_implementations)]
#![warn(missing_debug_implementations)] #![warn(missing_debug_implementations)]
// #![warn(missing_docs)] // #![warn(missing_docs)]
#![warn(non_ascii_idents)] #![warn(non_ascii_idents)]
@ -45,6 +45,7 @@ use bevy::{
core_pipeline::core_2d::Camera2dBundle, core_pipeline::core_2d::Camera2dBundle,
ecs::{ ecs::{
change_detection::ResMut, change_detection::ResMut,
component::Component,
query::{Changed, With}, query::{Changed, With},
system::{Commands, Query, Res}, system::{Commands, Query, Res},
}, },
@ -55,12 +56,12 @@ use bevy::{
Extent3d, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages, Extent3d, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages,
}, },
texture::{Image, ImageSettings}, texture::{Image, ImageSettings},
view::Visibility,
}, },
ui::{ ui::{
entity::{ButtonBundle, ImageBundle, TextBundle}, entity::{ButtonBundle, ImageBundle, NodeBundle, TextBundle},
widget::Button, widget::Button,
AlignItems, Interaction, JustifyContent, Size, Style, UiColor, UiImage, UiRect, Val, AlignItems, FocusPolicy, Interaction, JustifyContent, Size, Style, UiColor, UiImage,
UiRect, Val,
}, },
utils::default, utils::default,
window::{CursorIcon, WindowDescriptor, Windows}, window::{CursorIcon, WindowDescriptor, Windows},
@ -76,25 +77,30 @@ fn refresh_world_texture(images: &mut Assets<Image>, world_manager: &WorldManage
images.get_mut(&image_handle).unwrap().data = world_manager.world_color_bytes(); images.get_mut(&image_handle).unwrap().data = world_manager.world_color_bytes();
} }
#[cfg(feature = "render")]
#[derive(Component, Default)]
struct RainfallButton;
#[cfg(feature = "render")]
#[derive(Component, Default)]
struct TemperatureButton;
const NORMAL_BUTTON: Color = Color::rgb(0.15, 0.15, 0.15); const NORMAL_BUTTON: Color = Color::rgb(0.15, 0.15, 0.15);
const HOVERED_BUTTON: Color = Color::rgb(0.25, 0.25, 0.25); const HOVERED_BUTTON: Color = Color::rgb(0.25, 0.25, 0.25);
const PRESSED_BUTTON: Color = Color::rgb(0.35, 0.60, 0.35); const PRESSED_BUTTON: Color = Color::rgb(0.35, 0.60, 0.35);
#[cfg(feature = "render")] #[cfg(feature = "render")]
fn handle_button_interaction( fn handle_rainfall_button(
mut interaction_query: Query< mut interaction_query: Query<
'_, '_,
'_, '_,
(&Interaction, &mut UiColor /*, &Children*/), (&Interaction, &mut UiColor),
(Changed<Interaction>, With<Button>), (Changed<Interaction>, With<RainfallButton>),
>, >,
// mut text_query: Query<'_, '_, &mut Text>,
mut windows: ResMut<'_, Windows>, mut windows: ResMut<'_, Windows>,
mut images: ResMut<'_, Assets<Image>>, mut images: ResMut<'_, Assets<Image>>,
mut world_manager: ResMut<'_, WorldManager>, mut world_manager: ResMut<'_, WorldManager>,
) { ) {
for (interaction, mut color /*, children*/) in &mut interaction_query { for (interaction, mut color) in &mut interaction_query {
// let mut text = text_query.get_mut(children[0]).unwrap();
match *interaction { match *interaction {
Interaction::Clicked => { Interaction::Clicked => {
windows.primary_mut().set_cursor_icon(CursorIcon::Default); windows.primary_mut().set_cursor_icon(CursorIcon::Default);
@ -115,6 +121,39 @@ fn handle_button_interaction(
} }
} }
#[cfg(feature = "render")]
fn handle_temperature_button(
mut interaction_query: Query<
'_,
'_,
(&Interaction, &mut UiColor),
(Changed<Interaction>, With<TemperatureButton>),
>,
mut windows: ResMut<'_, Windows>,
mut images: ResMut<'_, Assets<Image>>,
mut world_manager: ResMut<'_, WorldManager>,
) {
for (interaction, mut color) in &mut interaction_query {
match *interaction {
Interaction::Clicked => {
windows.primary_mut().set_cursor_icon(CursorIcon::Default);
*color = PRESSED_BUTTON.into();
debug!("Toggling temperature");
world_manager.toggle_temperature();
refresh_world_texture(&mut images, &world_manager)
}
Interaction::Hovered => {
windows.primary_mut().set_cursor_icon(CursorIcon::Hand);
*color = HOVERED_BUTTON.into();
}
Interaction::None => {
windows.primary_mut().set_cursor_icon(CursorIcon::Default);
*color = NORMAL_BUTTON.into();
}
}
}
}
#[cfg(feature = "render")] #[cfg(feature = "render")]
fn generate_graphics( fn generate_graphics(
mut commands: Commands<'_, '_>, mut commands: Commands<'_, '_>,
@ -155,25 +194,30 @@ fn generate_graphics(
}) })
.with_children(|world_map| { .with_children(|world_map| {
_ = world_map _ = world_map
.spawn_bundle(NodeBundle {
style: Style {
size: Size::new(Val::Percent(100.0), Val::Undefined),
padding: UiRect::all(Val::Px(3.0)),
justify_content: JustifyContent::SpaceAround,
..default()
},
color: Color::NONE.into(),
focus_policy: FocusPolicy::Pass,
..default()
})
.with_children(|button_box| {
_ = button_box
.spawn_bundle(ButtonBundle { .spawn_bundle(ButtonBundle {
button: Button, button: Button,
style: Style { style: Style {
align_items: AlignItems::Center, align_items: AlignItems::Center,
justify_content: JustifyContent::Center, justify_content: JustifyContent::Center,
margin: UiRect {
left: Val::Auto,
right: Val::Px(20.0),
top: Val::Auto,
bottom: Val::Px(20.0),
..default()
},
padding: UiRect::all(Val::Px(2.0)),
..default() ..default()
}, },
color: NORMAL_BUTTON.into(), color: NORMAL_BUTTON.into(),
visibility: Visibility::visible(),
..default() ..default()
}) })
.insert(RainfallButton::default())
.with_children(|button| { .with_children(|button| {
_ = button.spawn_bundle(TextBundle { _ = button.spawn_bundle(TextBundle {
text: bevy::text::Text::from_section( text: bevy::text::Text::from_section(
@ -187,6 +231,32 @@ fn generate_graphics(
..default() ..default()
}); });
}); });
_ = button_box
.spawn_bundle(ButtonBundle {
button: Button,
style: Style {
align_items: AlignItems::Center,
justify_content: JustifyContent::Center,
..default()
},
color: NORMAL_BUTTON.into(),
..default()
})
.insert(TemperatureButton::default())
.with_children(|button| {
_ = button.spawn_bundle(TextBundle {
text: bevy::text::Text::from_section(
"Toggle temperature",
bevy::text::TextStyle {
font: asset_server.load("JuliaMono.ttf"),
font_size: 20.0,
color: Color::WHITE,
},
),
..default()
});
});
});
}); });
} }
@ -209,7 +279,8 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
..default() ..default()
}) })
.add_startup_system(generate_graphics) .add_startup_system(generate_graphics)
.add_system(handle_button_interaction); .add_system(handle_rainfall_button)
.add_system(handle_temperature_button);
} }
#[cfg(not(feature = "render"))] #[cfg(not(feature = "render"))]
{ {