diff --git a/build_all.sh.ps1 b/build_all.sh.ps1 index 42d840b..3a7c7b8 100755 --- a/build_all.sh.ps1 +++ b/build_all.sh.ps1 @@ -6,7 +6,7 @@ cargo build --no-default-features --features=logging && echo "Debug-build with features: render" && cargo build --no-default-features --features=render && echo "Debug-build with features: logging render" && -cargo build --no-default-features --features=logging,render && +cargo build --no-default-features --features="logging,render" && echo "Release-build with features: minimal" cargo build --release --no-default-features --features= && echo "Release-build with features: logging" && @@ -14,5 +14,5 @@ cargo build --release --no-default-features --features=logging && echo "Release-build with features: render" && cargo build --release --no-default-features --features=render && echo "Release-build with features: logging render" && -cargo build --release --no-default-features --features=logging,render && +cargo build --release --no-default-features --features="logging,render" && echo "Done!" \ No newline at end of file diff --git a/planet/src/lib.rs b/planet/src/lib.rs index 31c0daa..3852e83 100644 --- a/planet/src/lib.rs +++ b/planet/src/lib.rs @@ -41,4 +41,8 @@ pub use world_manager::WorldManager; pub(crate) mod macros; pub mod math_util; pub mod perlin; +#[cfg(feature = "render")] +pub mod rendering; +#[cfg(feature = "render")] +pub use rendering::*; pub mod saving; diff --git a/planet/src/macros.rs b/planet/src/macros.rs index d60c6e4..60f63a5 100644 --- a/planet/src/macros.rs +++ b/planet/src/macros.rs @@ -1,7 +1,7 @@ macro_rules! iterable_enum { ($Name:ident { $($Variant:ident),*$(,)? }) => { - #[derive(Debug, Copy, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize)] + #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, serde::Deserialize, serde::Serialize)] pub enum $Name { $($Variant),*, } @@ -13,6 +13,20 @@ macro_rules! iterable_enum { $Name::ITEMS.iter() } } + impl From<$Name> for &'static str { + fn from(value: $Name) -> &'static str { + match value { + $($Name::$Variant => stringify!($Variant),)* + } + } + } + impl From<&$Name> for &'static str { + fn from(value: &$Name) -> &'static str { + match value { + $($Name::$Variant => stringify!($Variant),)* + } + } + } } } pub(crate) use iterable_enum; diff --git a/planet/src/rendering/mod.rs b/planet/src/rendering/mod.rs new file mode 100644 index 0000000..56bc96c --- /dev/null +++ b/planet/src/rendering/mod.rs @@ -0,0 +1,45 @@ +#[cfg(feature = "render")] +use bevy::asset::HandleId; +use {crate::macros::iterable_enum, bevy::utils::HashSet}; + +iterable_enum!(WorldView { Biomes, Topography }); +iterable_enum!(WorldOverlay { + Temperature, + Rainfall +}); + +#[cfg(feature = "render")] +#[derive(Debug, Default)] +pub struct WorldRenderSettings { + pub map_image_handle_id: Option, + + visible_overlays: HashSet, + pub view: WorldView, +} + +#[cfg(feature = "render")] +impl WorldRenderSettings { + pub fn overlay_visible(&self, overlay: &WorldOverlay) -> bool { + self.visible_overlays.contains(overlay) + } + + pub fn toggle_overlay(&mut self, overlay: &WorldOverlay) { + if self.visible_overlays.contains(overlay) { + assert!( + self.visible_overlays.remove(overlay), + "Failed to remove overlay [{overlay:#?}], that shouldn't happen." + ); + } else { + assert!( + self.visible_overlays.insert(*overlay), + "Failed to insert overlay [{overlay:#?}], that shouldn't happen." + ); + } + } +} + +impl Default for WorldView { + fn default() -> Self { + WorldView::Biomes + } +} diff --git a/planet/src/world_manager.rs b/planet/src/world_manager.rs index 7537046..89a7f8c 100644 --- a/planet/src/world_manager.rs +++ b/planet/src/world_manager.rs @@ -1,7 +1,14 @@ -#[cfg(all(feature = "logging", feature = "render"))] -use bevy::log::debug; +#[cfg(feature = "render")] use { - crate::{macros::iterable_enum, World, WorldGenError}, + crate::{BiomeStats, TerrainCell, WorldOverlay, WorldRenderSettings, WorldView}, + bevy::{ + asset::Assets, + render::render_resource::Extent3d, + render::{color::Color, texture::Image}, + }, +}; +use { + crate::{World, WorldGenError}, bevy::{log::warn, utils::default}, rand::random, std::{ @@ -12,15 +19,6 @@ use { path::Path, }, }; -#[cfg(feature = "render")] -use { - crate::{BiomeStats, TerrainCell}, - bevy::{ - asset::{Assets, HandleId}, - render::render_resource::Extent3d, - render::{color::Color, texture::Image}, - }, -}; #[derive(Debug)] pub enum LoadError { @@ -85,71 +83,9 @@ impl Display for SaveError { } } -iterable_enum!(PlanetView { Biomes, Altitude }); - -#[cfg(feature = "render")] -#[derive(Debug, Default)] -pub struct WorldRenderSettings { - pub map_image_handle_id: Option, - - rainfall_visible: bool, - temperature_visible: bool, - view: PlanetView, -} - -#[cfg(feature = "render")] -impl WorldRenderSettings { - #[cfg(feature = "render")] - pub fn toggle_rainfall(&mut self) { - #[cfg(feature = "logging")] - if self.rainfall_visible { - debug!("Turning rainfall off"); - } else { - debug!("Turning rainfall on"); - } - self.rainfall_visible = !self.rainfall_visible; - } - - #[cfg(feature = "render")] - pub fn toggle_temperature(&mut self) { - #[cfg(feature = "logging")] - if self.temperature_visible { - debug!("Turning temperature off"); - } else { - debug!("Turning temperature on"); - } - self.temperature_visible = !self.temperature_visible; - } - - #[cfg(feature = "render")] - pub fn cycle_view(&mut self) { - let idx = (PlanetView::iterator() - .position(|view| *view == self.view) - .unwrap() - + 1) - % PlanetView::ITEM_COUNT; - #[cfg(feature = "logging")] - debug!( - "Cycling view from {:#?} to {:#?}", - self.view, - PlanetView::ITEMS[idx] - ); - self.view = PlanetView::ITEMS[idx]; - } -} - -impl Default for PlanetView { - fn default() -> Self { - PlanetView::Biomes - } -} - #[derive(Debug, Default)] pub struct WorldManager { world: Option, - - #[cfg(feature = "render")] - pub render_settings: WorldRenderSettings, } impl WorldManager { @@ -191,6 +127,7 @@ impl WorldManager { pub fn load_world>( &mut self, path: P, + #[cfg(feature = "render")] render_settings: &WorldRenderSettings, #[cfg(feature = "render")] images: &mut Assets, ) -> Result<(), LoadError> { let mut file = match File::open(path) { @@ -214,7 +151,7 @@ impl WorldManager { #[cfg(feature = "render")] { let image_handle = &images.get_handle( - self.render_settings + render_settings .map_image_handle_id .expect("Missing image handle, even though world is present"), ); @@ -264,8 +201,8 @@ impl WorldManager { #[cfg(feature = "render")] #[must_use] - fn generate_color(&self, cell: &TerrainCell) -> Color { - if self.render_settings.view == PlanetView::Biomes { + fn generate_color(&self, cell: &TerrainCell, render_settings: &WorldRenderSettings) -> Color { + if render_settings.view == WorldView::Biomes { return WorldManager::biome_color(cell); } @@ -282,7 +219,7 @@ impl WorldManager { let mut green = altitude_color.g(); let mut blue = altitude_color.b(); - if self.render_settings.rainfall_visible { + if render_settings.overlay_visible(&WorldOverlay::Rainfall) { layer_count += 1.0; let rainfall_color = self.rainfall_contour_color(cell.rainfall); // if self.contours { @@ -296,7 +233,7 @@ impl WorldManager { blue += rainfall_color.b(); } - if self.render_settings.temperature_visible { + if render_settings.overlay_visible(&WorldOverlay::Temperature) { layer_count += 1.0; let temperature_color = self.temperature_contour_color(cell.temperature); // if self.contours { @@ -399,14 +336,14 @@ impl WorldManager { #[cfg(feature = "render")] #[must_use] - pub fn map_color_bytes(&self) -> Vec { + pub fn map_color_bytes(&self, render_settings: &WorldRenderSettings) -> Vec { self.world() .terrain .iter() .rev() .flatten() .flat_map(|cell| { - self.generate_color(cell) + self.generate_color(cell, render_settings) .as_rgba_f32() .iter() .flat_map(|num| num.to_le_bytes()) diff --git a/src/gui/mod.rs b/src/gui/mod.rs index d49686d..ba52cd3 100644 --- a/src/gui/mod.rs +++ b/src/gui/mod.rs @@ -10,18 +10,20 @@ use { crate::gui::{open_window, WidgetId, WidgetSystem}, bevy::{ asset::Assets, - ecs::change_detection::Mut, log::debug, render::{render_resource::Extent3d, texture::Image}, }, - planet::WorldManager, + planet::{WorldManager, WorldRenderSettings}, }; -fn update_textures(world_manager: &WorldManager, images: &mut Mut>) { +pub(crate) fn update_textures( + world_manager: &WorldManager, + render_settings: &WorldRenderSettings, + images: &mut Assets, +) { debug!("refreshing world texture"); let map_image_handle = images.get_handle( - world_manager - .render_settings + render_settings .map_image_handle_id .expect("No map image handle"), ); @@ -33,5 +35,5 @@ fn update_textures(world_manager: &WorldManager, images: &mut Mut> height: world_manager.world().height, depth_or_array_layers: 1, }); - map_image.data = world_manager.map_color_bytes(); + map_image.data = world_manager.map_color_bytes(render_settings); } diff --git a/src/gui/widgets/toolbar.rs b/src/gui/widgets/toolbar.rs index 8c09aef..9174767 100644 --- a/src/gui/widgets/toolbar.rs +++ b/src/gui/widgets/toolbar.rs @@ -1,6 +1,12 @@ use { crate::{ - gui::{open_window, update_textures, windows::Overlay, WidgetId, WidgetSystem}, + gui::{ + open_window, + update_textures, + windows::{SaveLoad, WorldOverlaySelection, WorldViewSelection}, + WidgetId, + WidgetSystem, + }, macros::iterable_enum, resources::OpenedWindows, }, @@ -16,50 +22,48 @@ use { render::texture::Image, }, bevy_egui::egui::{Layout, Ui}, - planet::WorldManager, + planet::{WorldManager, WorldRenderSettings}, std::marker::PhantomData, }; iterable_enum!(ToolbarButton { GenerateWorld, - SaveWorld, - LoadWorld, + SaveLoad, + Views, Overlays, - ToggleBiomes, }); impl ToolbarButton { fn clicked(self, world: &mut World) { world.resource_scope(|world, mut world_manager: Mut<'_, WorldManager>| { - match self { - ToolbarButton::GenerateWorld => { - if let Err(err) = world_manager.new_world() { - eprintln!("Failed to generate world: {}", err); - } else { - update_textures(&world_manager, &mut world.resource_mut::>()); - } - }, - ToolbarButton::SaveWorld => { - if let Err(err) = world_manager.save_world("planet.ron") { - eprintln!("Failed to save planet.ron: {}", err); - } - }, - ToolbarButton::LoadWorld => { - let mut images = world.resource_mut::>(); - if let Err(err) = world_manager.load_world("planet.ron", &mut images) { - eprintln!("Failed to load planet.ron: {}", err); - } else { - update_textures(&world_manager, &mut images); - } - }, - ToolbarButton::Overlays => { - open_window::(&mut world.resource_mut::()); - }, - ToolbarButton::ToggleBiomes => { - world_manager.render_settings.cycle_view(); - update_textures(&world_manager, &mut world.resource_mut::>()); - }, - }; + world.resource_scope(|world, render_settings: Mut<'_, WorldRenderSettings>| { + match self { + ToolbarButton::GenerateWorld => { + if let Err(err) = world_manager.new_world() { + eprintln!("Failed to generate world: {}", err); + } else { + update_textures( + &world_manager, + &render_settings, + &mut world.resource_mut::>(), + ); + } + }, + ToolbarButton::SaveLoad => { + open_window::(&mut world.resource_mut::()); + }, + ToolbarButton::Views => { + open_window::( + &mut world.resource_mut::(), + ); + }, + ToolbarButton::Overlays => { + open_window::( + &mut world.resource_mut::(), + ); + }, + }; + }); }); } } @@ -67,11 +71,10 @@ impl ToolbarButton { impl From for &'static str { fn from(button: ToolbarButton) -> Self { match button { + ToolbarButton::Views => "Change view", ToolbarButton::Overlays => "Overlays", - ToolbarButton::ToggleBiomes => "Toggle biome view", ToolbarButton::GenerateWorld => "Generate new world", - ToolbarButton::SaveWorld => "Save", - ToolbarButton::LoadWorld => "Load", + ToolbarButton::SaveLoad => "Save/Load", } } } diff --git a/src/gui/window.rs b/src/gui/window.rs index f13b4df..83cbabc 100644 --- a/src/gui/window.rs +++ b/src/gui/window.rs @@ -21,10 +21,13 @@ pub(crate) trait WindowSystem: SystemParam { } pub(crate) fn render_windows(world: &mut World, ctx: &Context) { - // TODO: Windows are hard-coded here instead of being iterable. - // Is that good enough? Probably, yea. - window::(world, ctx); + // TODO: Windows are hard-coded here instead of being iterable, and allows + // creating new windows that are never rendered. + // Is that good enough? window::(world, ctx); + window::(world, ctx); + window::(world, ctx); + window::(world, ctx); } pub(crate) fn open_window(windows: &mut OpenedWindows) { diff --git a/src/gui/windows/mod.rs b/src/gui/windows/mod.rs index 2cf1f28..6ca2797 100644 --- a/src/gui/windows/mod.rs +++ b/src/gui/windows/mod.rs @@ -1,4 +1,8 @@ -mod overlay; -pub(crate) use overlay::Overlay; mod tile_info; pub(crate) use tile_info::TileInfo; +mod world_view_selection; +pub(crate) use world_view_selection::WorldViewSelection; +mod world_overlay_selection; +pub(crate) use world_overlay_selection::WorldOverlaySelection; +mod save_load; +pub(crate) use save_load::SaveLoad; \ No newline at end of file diff --git a/src/gui/windows/overlay.rs b/src/gui/windows/overlay.rs deleted file mode 100644 index 3e4b0ce..0000000 --- a/src/gui/windows/overlay.rs +++ /dev/null @@ -1,25 +0,0 @@ -use { - crate::gui::WindowSystem, - bevy::ecs::{ - system::{SystemParam, SystemState}, - world::World, - }, - bevy_egui::egui::Ui, - std::marker::PhantomData, -}; - -#[derive(SystemParam)] -pub(crate) struct Overlay<'w, 's> { - #[system_param(ignore)] - _phantom: PhantomData<(&'w (), &'s ())>, -} - -impl WindowSystem for Overlay<'_, '_> { - fn draw_contents(world: &mut World, _state: &mut SystemState, ui: &mut Ui) { - ui.label(format!("{world:#?}")); - } - - fn name() -> &'static str { - "Overlay Selection" - } -} diff --git a/src/gui/windows/save_load.rs b/src/gui/windows/save_load.rs new file mode 100644 index 0000000..145f1c9 --- /dev/null +++ b/src/gui/windows/save_load.rs @@ -0,0 +1,57 @@ +use { + crate::gui::WindowSystem, + bevy::{ + ecs::{ + change_detection::Mut, + system::{Local, SystemParam, SystemState}, + world::World, + }, + log::error, + prelude::{Assets, Image}, + }, + bevy_egui::egui::Ui, + planet::{WorldManager, WorldRenderSettings}, + std::marker::PhantomData, +}; + +#[derive(SystemParam)] +pub(crate) struct SaveLoad<'w, 's> { + pub file_name: Local<'s, String>, + #[system_param(ignore)] + _phantom: PhantomData<(&'w (), &'s ())>, +} + +impl WindowSystem for SaveLoad<'_, '_> { + fn draw_contents(world: &mut World, state: &mut SystemState, ui: &mut Ui) { + world.resource_scope(|world, mut world_manager: Mut| { + world.resource_scope(|world, mut images: Mut>| { + world.resource_scope(|world, render_settings: Mut| { + let mut state = state.get_mut(world); + + // TODO: File selection dialog. + ui.text_edit_singleline(&mut *state.file_name); + + if ui.button("Save").clicked() { + if let Err(err) = world_manager.save_world(&*state.file_name) { + // TODO: Error popup + error!("Failed to save: {err:#?}"); + } + } + if ui.button("Load").clicked() { + if let Err(err) = world_manager.load_world( + &*state.file_name, + &render_settings, + &mut images, + ) { + error!("Failed to load: {err:#?}"); + } + } + }); + }); + }); + } + + fn name() -> &'static str { + "Save/Load world" + } +} diff --git a/src/gui/windows/world_overlay_selection.rs b/src/gui/windows/world_overlay_selection.rs new file mode 100644 index 0000000..001622a --- /dev/null +++ b/src/gui/windows/world_overlay_selection.rs @@ -0,0 +1,50 @@ +use { + crate::gui::{update_textures, WindowSystem}, + bevy::{ + asset::Assets, + ecs::{ + change_detection::Mut, + system::{SystemParam, SystemState}, + world::World, + }, + render::texture::Image, + }, + bevy_egui::egui::Ui, + planet::{WorldManager, WorldOverlay, WorldRenderSettings}, + std::marker::PhantomData, +}; + +#[derive(SystemParam)] +pub(crate) struct WorldOverlaySelection<'w, 's> { + #[system_param(ignore)] + _phantom: PhantomData<(&'w (), &'s ())>, +} + +impl WindowSystem for WorldOverlaySelection<'_, '_> { + fn draw_contents(world: &mut World, _state: &mut SystemState, ui: &mut Ui) { + world.resource_scope(|world, mut render_settings: Mut| { + for overlay in WorldOverlay::iterator() { + if ui + .selectable_label( + render_settings.overlay_visible(overlay), + <&'static str>::from(overlay), + ) + .clicked() + { + render_settings.toggle_overlay(overlay); + world.resource_scope(|world, mut images: Mut>| { + update_textures( + world.resource::(), + &render_settings, + &mut images, + ); + }); + } + } + }); + } + + fn name() -> &'static str { + "Overlay Selection" + } +} diff --git a/src/gui/windows/world_view_selection.rs b/src/gui/windows/world_view_selection.rs new file mode 100644 index 0000000..7c9e6df --- /dev/null +++ b/src/gui/windows/world_view_selection.rs @@ -0,0 +1,50 @@ +use { + crate::gui::{update_textures, WindowSystem}, + bevy::{ + asset::Assets, + ecs::{ + change_detection::Mut, + system::{SystemParam, SystemState}, + world::World, + }, + render::texture::Image, + }, + bevy_egui::egui::Ui, + planet::{WorldManager, WorldRenderSettings, WorldView}, + std::marker::PhantomData, +}; + +#[derive(SystemParam)] +pub(crate) struct WorldViewSelection<'w, 's> { + #[system_param(ignore)] + _phantom: PhantomData<(&'w (), &'s ())>, +} + +impl WindowSystem for WorldViewSelection<'_, '_> { + fn draw_contents(world: &mut World, _state: &mut SystemState, ui: &mut Ui) { + world.resource_scope(|world, mut render_settings: Mut| { + let current_selection = render_settings.view; + for view in WorldView::iterator() { + let view = *view; + if ui + .selectable_label(view == current_selection, <&'static str>::from(view)) + .clicked() + && render_settings.view != view + { + render_settings.view = view; + world.resource_scope(|world, mut images: Mut>| { + update_textures( + world.resource::(), + &render_settings, + &mut images, + ); + }); + } + } + }); + } + + fn name() -> &'static str { + "View Selection" + } +} diff --git a/src/main.rs b/src/main.rs index 5ffc00b..9fd48c4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,5 @@ +#![cfg_attr(not(feature = "logging"), windows_subsystem = "windows")] + pub(crate) mod components; #[cfg(feature = "render")] pub(crate) mod gui; @@ -35,13 +37,7 @@ use { prelude::Vec2, render::{ camera::{Camera, RenderTarget}, - render_resource::{ - Extent3d, - TextureDescriptor, - TextureDimension, - TextureFormat, - TextureUsages, - }, + render_resource::{TextureDescriptor, TextureDimension, TextureFormat, TextureUsages}, texture::{Image, ImageSettings}, }, sprite::{Sprite, SpriteBundle}, @@ -54,7 +50,15 @@ use { EguiContext, }, components::panning::Pan2d, - gui::{render_windows, widget, widgets::ToolbarWidget, window::open_window, windows::TileInfo}, + gui::{ + render_windows, + update_textures, + widget, + widgets::ToolbarWidget, + window::open_window, + windows::TileInfo, + }, + planet::WorldRenderSettings, resources::{CursorMapPosition, OpenedWindows}, }; @@ -93,9 +97,10 @@ fn update_cursor_map_position( #[cfg(feature = "render")] fn generate_graphics( mut commands: Commands<'_, '_>, - mut world_manager: ResMut<'_, WorldManager>, + world_manager: ResMut<'_, WorldManager>, mut images: ResMut<'_, Assets>, mut egui_context: ResMut<'_, EguiContext>, + mut render_settings: ResMut<'_, WorldRenderSettings>, ) { // Add Julia-Mono font to egui { @@ -127,14 +132,10 @@ fn generate_graphics( // Set up 2D map mode { let map_image_handle = images.add(Image { - data: world_manager.map_color_bytes(), + data: vec![], texture_descriptor: TextureDescriptor { label: None, - size: Extent3d { - width: world.width, - height: world.height, - ..default() - }, + size: default(), dimension: TextureDimension::D2, format: TextureFormat::Rgba32Float, mip_level_count: 1, @@ -143,14 +144,14 @@ fn generate_graphics( }, ..default() }); - world_manager.render_settings.map_image_handle_id = Some(map_image_handle.id); + render_settings.map_image_handle_id = Some(map_image_handle.id); _ = commands .spawn_bundle(Camera2dBundle::default()) .insert(Pan2d::new()); // TODO: Switch to egui _ = commands.spawn_bundle(SpriteBundle { - texture: images.get_handle(world_manager.render_settings.map_image_handle_id.unwrap()), + texture: images.get_handle(map_image_handle.id), sprite: Sprite { custom_size: Some(custom_sprite_size), ..default() @@ -159,6 +160,7 @@ fn generate_graphics( }); } + update_textures(&world_manager, &render_settings, &mut images); } #[cfg(feature = "render")] @@ -222,6 +224,7 @@ fn main() -> Result<(), Box> { }) .insert_resource(CursorMapPosition::default()) .insert_resource(OpenedWindows::default()) + .insert_resource(WorldRenderSettings::default()) .add_startup_system(generate_graphics) .add_system(update_gui.exclusive_system()) .add_system(update_cursor_map_position)