Allow toggling rainfall
c8bbdf19d9964eb8190daa8a19b3298dd90a60b5
This commit is contained in:
parent
f4cf1b4463
commit
2c85d95b7a
6 changed files with 194 additions and 49 deletions
|
@ -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
BIN
assets/JuliaMono.ttf
Normal file
Binary file not shown.
|
@ -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,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
133
src/main.rs
133
src/main.rs
|
@ -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,15 +141,52 @@ 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
|
||||||
|
.spawn_bundle(ImageBundle {
|
||||||
style: Style {
|
style: Style {
|
||||||
size: Size::new(Val::Auto, Val::Auto),
|
size: Size::new(Val::Percent(100.0), Val::Auto),
|
||||||
..default()
|
..default()
|
||||||
},
|
},
|
||||||
image: UiImage(image_handle),
|
image: UiImage(image_handle),
|
||||||
..default()
|
..default()
|
||||||
|
})
|
||||||
|
.with_children(|world_map| {
|
||||||
|
_ = 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()
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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(())
|
||||||
|
|
|
@ -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())
|
||||||
|
|
Loading…
Reference in a new issue