Allow toggling rainfall

c8bbdf19d9964eb8190daa8a19b3298dd90a60b5
This commit is contained in:
Tobias Berger 2022-09-06 11:07:13 +02:00
parent f4cf1b4463
commit 2c85d95b7a
Signed by: toby
GPG key ID: 2D05EFAB764D6A88
6 changed files with 194 additions and 49 deletions

View file

@ -6,7 +6,7 @@ edition = "2021"
[features] [features]
debug = ["save/debug"] debug = ["save/debug"]
render = ["bevy/bevy_asset", "bevy/bevy_winit", "bevy/render", "save/render"] render = ["bevy/bevy_asset", "bevy/bevy_winit", "bevy/render", "save/render"]
default = ["render"] default = ["render", "debug"]
[dependencies.save] [dependencies.save]
path = "save" path = "save"

BIN
assets/JuliaMono.ttf Normal file

Binary file not shown.

View file

@ -71,6 +71,13 @@ impl Debug for World {
} }
} }
#[derive(Debug, Copy, Clone, Default)]
pub struct Biome {
pub altitude: f32,
pub rainfall: f32,
pub temperature: f32,
}
#[derive(Debug, Copy, Clone, Default)] #[derive(Debug, Copy, Clone, Default)]
pub struct TerrainCell { pub struct TerrainCell {
pub altitude: f32, pub altitude: f32,
@ -120,7 +127,7 @@ 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_alt() { if let Err(err) = self.generate_rainfall() {
return Err(WorldGenError::CartesianError(err)); return Err(WorldGenError::CartesianError(err));
} }
@ -280,6 +287,7 @@ 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> { fn generate_rainfall_alt(&mut self) -> Result<(), CartesianError> {
let max_cycles = self.width / 5; let max_cycles = self.width / 5;
@ -289,13 +297,12 @@ impl World {
for _ in 0..max_cycles { for _ in 0..max_cycles {
for x in 0..self.width { for x in 0..self.width {
let mut prev_x = (x - 1 + self.width) % self.width;
for y in 0..self.height { for y in 0..self.height {
let mut prev_x = (x - 1 + self.width) % self.width;
let prev_y = if y < self.height / 4 { let prev_y = if y < self.height / 4 {
prev_x = (x + 1) % self.width;
y + 1 y + 1
} else if y < self.height / 2 { } else if y < self.height / 2 {
prev_x = (x + 1) % self.width;
y - 1 y - 1
} else if y < (self.height * 3) / 4 { } else if y < (self.height * 3) / 4 {
prev_x = (x + 1) % self.width; prev_x = (x + 1) % self.width;
@ -304,15 +311,12 @@ impl World {
y - 1 y - 1
}; };
let width_factor = f32::sin(PI * y as f32 / self.height as f32);
let mut cell = self.terrain[y as usize][x as usize]; let mut cell = self.terrain[y as usize][x as usize];
cell.previous_rain_accumulated = cell.rain_accumulated; cell.previous_rain_accumulated = cell.rain_accumulated;
cell.rain_accumulated = 0.0; cell.rain_accumulated = 0.0;
if cell.altitude <= 0.0 { if cell.altitude <= 0.0 {
cell.rain_accumulated += cell.rain_accumulated += ACCUMULATED_RAIN_FACTOR * Self::MAX_RAINFALL;
width_factor * ACCUMULATED_RAIN_FACTOR * Self::MAX_RAINFALL;
} }
let prev_cell = self.terrain[prev_y as usize][prev_x as usize]; let prev_cell = self.terrain[prev_y as usize][prev_x as usize];
@ -342,8 +346,8 @@ impl World {
Ok(()) 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;
@ -351,14 +355,16 @@ impl World {
for y in 0..self.terrain.len() { for y in 0..self.terrain.len() {
let alpha = (y as f32 / self.height as f32) * PI; let alpha = (y as f32 / self.height as f32) * PI;
for x in 0..self.terrain[y].len() { for x in 0..self.terrain[y].len() {
let mut cell = self.terrain[y][x];
let beta = (x as f32 / self.width as f32) * TAU; let beta = (x as f32 / self.width as f32) * TAU;
let base_rainfall = Self::calculate_rainfall( let value =
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 base_rainfall = Self::calculate_rainfall(value);
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,
1.0, 1.0,
); );
@ -371,8 +377,10 @@ impl World {
} }
fn calculate_rainfall(raw_rainfall: f32) -> f32 { fn calculate_rainfall(raw_rainfall: f32) -> f32 {
((raw_rainfall * Self::RAINFALL_ALTITUDE_FACTOR) + Self::MIN_RAINFALL) f32::clamp(
.clamp(0.0, Self::MAX_RAINFALL) (raw_rainfall * Self::RAINFALL_SPAN) + Self::MIN_RAINFALL,
0.0,
Self::MAX_RAINFALL,
)
} }
*/
} }

View file

@ -1,17 +1,42 @@
#[cfg(feature = "render")] #[cfg(feature = "render")]
use crate::TerrainCell; use crate::TerrainCell;
use crate::{World, WorldGenError}; use crate::{World, WorldGenError};
use bevy::log::debug;
#[cfg(feature = "render")] #[cfg(feature = "render")]
use bevy::render::color::Color; use bevy::{
asset::HandleId,
render::{color::Color, texture::Image},
};
use rand::random; use rand::random;
#[derive(Debug)] #[derive(Debug)]
pub struct WorldManager { pub struct WorldManager {
#[cfg(feature = "render")]
pub image_handle_id: HandleId,
world: Option<World>, world: Option<World>,
#[cfg(feature = "render")]
rainfall_visible: bool,
} }
impl WorldManager { impl WorldManager {
pub fn new() -> WorldManager { pub fn new() -> WorldManager {
WorldManager { world: None } Self {
#[cfg(feature = "render")]
image_handle_id: HandleId::default::<Image>(),
world: None,
rainfall_visible: false,
}
}
#[cfg(feature = "render")]
pub fn toggle_rainfall(&mut self) {
if self.rainfall_visible {
debug!("Turning rainfall off");
} else {
debug!("Turning rainfall on");
debug!("World: {:#?}", self.world);
}
self.rainfall_visible = !self.rainfall_visible;
} }
pub fn get_world(&self) -> Option<&World> { pub fn get_world(&self) -> Option<&World> {
@ -27,9 +52,13 @@ impl WorldManager {
} }
#[cfg(feature = "render")] #[cfg(feature = "render")]
fn generate_color(cell: &TerrainCell) -> Color { fn generate_color(cell: &TerrainCell, show_rainfall: bool) -> Color {
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 = if show_rainfall {
Self::rainfall_color(cell.rainfall)
} else {
Color::BLACK
};
let normalized_rainfall = Self::normalize_rainfall(cell.rainfall); let normalized_rainfall = Self::normalize_rainfall(cell.rainfall);
@ -73,20 +102,15 @@ impl WorldManager {
#[cfg(feature = "render")] #[cfg(feature = "render")]
fn rainfall_color(rainfall: f32) -> Color { fn rainfall_color(rainfall: f32) -> Color {
if rainfall <= 0.0 { Color::rgb(0.0, Self::normalize_rainfall(rainfall), 0.0)
Color::BLACK
} else {
let mult = rainfall / World::MAX_RAINFALL;
Color::rgb(0.0, mult, 0.0)
}
} }
#[cfg(feature = "render")] #[cfg(feature = "render")]
fn normalize_rainfall(rainfall: f32) -> f32 { fn normalize_rainfall(rainfall: f32) -> f32 {
if rainfall <= 0.0 { if rainfall <= 0.0 {
rainfall 0.0
} else { } else {
rainfall / World::MAX_ALTITUDE rainfall / World::MAX_RAINFALL
} }
} }
@ -99,7 +123,7 @@ impl WorldManager {
terrain_cells terrain_cells
.iter() .iter()
.map(|cell| Self::generate_color(cell)) .map(|cell| Self::generate_color(cell, self.rainfall_visible))
.collect() .collect()
} }
} }

View file

@ -34,34 +34,93 @@
mod plugins; mod plugins;
use bevy::app::App; use bevy::{
app::App,
log::{debug, LogSettings},
utils::tracing::Level,
};
#[cfg(feature = "render")] #[cfg(feature = "render")]
use bevy::{ use bevy::{
asset::Assets, asset::{AssetServer, Assets},
core_pipeline::core_2d::Camera2dBundle, core_pipeline::core_2d::Camera2dBundle,
ecs::{ ecs::{
change_detection::ResMut, change_detection::ResMut,
system::{Commands, Res}, query::{Changed, With},
system::{Commands, Query, Res},
}, },
hierarchy::BuildChildren,
render::{ render::{
color::Color,
render_resource::{ render_resource::{
Extent3d, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages, Extent3d, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages,
}, },
texture::{Image, ImageSettings}, texture::{Image, ImageSettings},
view::Visibility,
},
ui::{
entity::{ButtonBundle, ImageBundle, TextBundle},
widget::Button,
AlignItems, Interaction, JustifyContent, Size, Style, UiColor, UiImage, UiRect, Val,
}, },
ui::{entity::ImageBundle, Size, Style, UiImage, Val},
utils::default, utils::default,
window::WindowDescriptor, window::{CursorIcon, WindowDescriptor, Windows},
winit::WinitSettings, winit::WinitSettings,
}; };
use plugins::WorldPlugins; use plugins::WorldPlugins;
use save::*; use save::*;
#[cfg(feature = "render")] #[cfg(feature = "render")]
fn generate_texture( fn refresh_world_texture(images: &mut Assets<Image>, world_manager: &WorldManager) {
debug!("refreshing world texture");
let image_handle = images.get_handle(world_manager.image_handle_id);
images.get_mut(&image_handle).unwrap().data = world_manager.world_color_bytes();
}
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 PRESSED_BUTTON: Color = Color::rgb(0.35, 0.60, 0.35);
#[cfg(feature = "render")]
fn handle_button_interaction(
mut interaction_query: Query<
'_,
'_,
(&Interaction, &mut UiColor /*, &Children*/),
(Changed<Interaction>, With<Button>),
>,
// mut text_query: Query<'_, '_, &mut Text>,
mut windows: ResMut<'_, Windows>,
mut images: ResMut<'_, Assets<Image>>,
mut world_manager: ResMut<'_, WorldManager>,
) {
for (interaction, mut color /*, children*/) in &mut interaction_query {
// let mut text = text_query.get_mut(children[0]).unwrap();
match *interaction {
Interaction::Clicked => {
windows.primary_mut().set_cursor_icon(CursorIcon::Default);
*color = PRESSED_BUTTON.into();
debug!("Toggling rainfall");
world_manager.toggle_rainfall();
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")]
fn generate_graphics(
mut commands: Commands<'_, '_>, mut commands: Commands<'_, '_>,
mut images: ResMut<'_, Assets<Image>>, mut images: ResMut<'_, Assets<Image>>,
world_manager: Res<'_, WorldManager>, mut world_manager: ResMut<'_, WorldManager>,
asset_server: Res<'_, AssetServer>,
) { ) {
let world = world_manager.get_world().unwrap(); let world = world_manager.get_world().unwrap();
@ -82,16 +141,53 @@ fn generate_texture(
}, },
..default() ..default()
}); });
world_manager.image_handle_id = image_handle.id;
_ = commands.spawn_bundle(Camera2dBundle::default()); _ = commands.spawn_bundle(Camera2dBundle::default());
_ = commands.spawn_bundle(ImageBundle { _ = commands
style: Style { .spawn_bundle(ImageBundle {
size: Size::new(Val::Auto, Val::Auto), style: Style {
size: Size::new(Val::Percent(100.0), Val::Auto),
..default()
},
image: UiImage(image_handle),
..default() ..default()
}, })
image: UiImage(image_handle), .with_children(|world_map| {
..default() _ = world_map
}); .spawn_bundle(ButtonBundle {
button: Button,
style: Style {
align_items: AlignItems::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()
},
color: NORMAL_BUTTON.into(),
visibility: Visibility::visible(),
..default()
})
.with_children(|button| {
_ = button.spawn_bundle(TextBundle {
text: bevy::text::Text::from_section(
"Toggle rainfall",
bevy::text::TextStyle {
font: asset_server.load("JuliaMono.ttf"),
font_size: 20.0,
color: Color::WHITE,
},
),
..default()
});
});
});
} }
fn main() -> Result<(), Box<dyn std::error::Error>> { fn main() -> Result<(), Box<dyn std::error::Error>> {
@ -112,12 +208,29 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
resizable: true, resizable: true,
..default() ..default()
}) })
.add_startup_system(generate_texture); .add_startup_system(generate_graphics)
.add_system(handle_button_interaction);
} }
#[cfg(not(feature = "render"))] #[cfg(not(feature = "render"))]
{ {
_ = manager.new_world()? _ = manager.new_world()?
} }
#[cfg(feature = "debug")]
{
_ = app.insert_resource(LogSettings {
level: Level::DEBUG,
..default()
});
}
#[cfg(not(feature = "debug"))]
{
_ = app.insert_resource(LogSettings {
level: Level::WARN,
..default()
});
}
app.insert_resource(manager).add_plugins(WorldPlugins).run(); app.insert_resource(manager).add_plugins(WorldPlugins).run();
Ok(()) Ok(())

View file

@ -18,8 +18,8 @@ impl PluginGroup for WorldPlugins {
#[cfg(feature = "render")] #[cfg(feature = "render")]
{ {
use bevy::{ use bevy::{
asset::AssetPlugin, core_pipeline::CorePipelinePlugin, input::InputPlugin, asset::AssetPlugin, core_pipeline::CorePipelinePlugin, hierarchy::HierarchyPlugin,
render::RenderPlugin, sprite::SpritePlugin, text::TextPlugin, input::InputPlugin, render::RenderPlugin, sprite::SpritePlugin, text::TextPlugin,
transform::TransformPlugin, ui::UiPlugin, window::WindowPlugin, winit::WinitPlugin, transform::TransformPlugin, ui::UiPlugin, window::WindowPlugin, winit::WinitPlugin,
}; };
_ = group _ = group
@ -28,7 +28,7 @@ impl PluginGroup for WorldPlugins {
.add(InputPlugin::default()) .add(InputPlugin::default())
.add(WindowPlugin::default()) .add(WindowPlugin::default())
.add(AssetPlugin::default()) .add(AssetPlugin::default())
// scene .add(HierarchyPlugin::default())
.add(WinitPlugin::default()) .add(WinitPlugin::default())
.add(RenderPlugin::default()) .add(RenderPlugin::default())
.add(CorePipelinePlugin::default()) .add(CorePipelinePlugin::default())