Add more windows, move rendering logic

This commit is contained in:
Tobias Berger 2022-11-08 21:46:32 +01:00
parent 23eed81343
commit a38cafc7b8
Signed by: toby
GPG key ID: 2D05EFAB764D6A88
14 changed files with 321 additions and 174 deletions

View file

@ -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!"

View file

@ -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;

View file

@ -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;

View file

@ -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<HandleId>,
visible_overlays: HashSet<WorldOverlay>,
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
}
}

View file

@ -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<HandleId>,
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<World>,
#[cfg(feature = "render")]
pub render_settings: WorldRenderSettings,
}
impl WorldManager {
@ -191,6 +127,7 @@ impl WorldManager {
pub fn load_world<P: AsRef<Path>>(
&mut self,
path: P,
#[cfg(feature = "render")] render_settings: &WorldRenderSettings,
#[cfg(feature = "render")] images: &mut Assets<Image>,
) -> 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<u8> {
pub fn map_color_bytes(&self, render_settings: &WorldRenderSettings) -> Vec<u8> {
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())

View file

@ -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<Assets<Image>>) {
pub(crate) fn update_textures(
world_manager: &WorldManager,
render_settings: &WorldRenderSettings,
images: &mut Assets<Image>,
) {
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<Assets<Image>>
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);
}

View file

@ -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,62 +22,59 @@ 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>| {
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, &mut world.resource_mut::<Assets<Image>>());
update_textures(
&world_manager,
&render_settings,
&mut world.resource_mut::<Assets<Image>>(),
);
}
},
ToolbarButton::SaveWorld => {
if let Err(err) = world_manager.save_world("planet.ron") {
eprintln!("Failed to save planet.ron: {}", err);
}
ToolbarButton::SaveLoad => {
open_window::<SaveLoad>(&mut world.resource_mut::<OpenedWindows>());
},
ToolbarButton::LoadWorld => {
let mut images = world.resource_mut::<Assets<Image>>();
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::Views => {
open_window::<WorldViewSelection>(
&mut world.resource_mut::<OpenedWindows>(),
);
},
ToolbarButton::Overlays => {
open_window::<Overlay>(&mut world.resource_mut::<OpenedWindows>());
},
ToolbarButton::ToggleBiomes => {
world_manager.render_settings.cycle_view();
update_textures(&world_manager, &mut world.resource_mut::<Assets<Image>>());
open_window::<WorldOverlaySelection>(
&mut world.resource_mut::<OpenedWindows>(),
);
},
};
});
});
}
}
impl From<ToolbarButton> 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",
}
}
}

View file

@ -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::<windows::Overlay>(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::<windows::TileInfo>(world, ctx);
window::<windows::WorldViewSelection>(world, ctx);
window::<windows::WorldOverlaySelection>(world, ctx);
window::<windows::SaveLoad>(world, ctx);
}
pub(crate) fn open_window<S: 'static + WindowSystem>(windows: &mut OpenedWindows) {

View file

@ -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;

View file

@ -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<Self>, ui: &mut Ui) {
ui.label(format!("{world:#?}"));
}
fn name() -> &'static str {
"Overlay Selection"
}
}

View file

@ -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<Self>, ui: &mut Ui) {
world.resource_scope(|world, mut world_manager: Mut<WorldManager>| {
world.resource_scope(|world, mut images: Mut<Assets<Image>>| {
world.resource_scope(|world, render_settings: Mut<WorldRenderSettings>| {
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"
}
}

View file

@ -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<Self>, ui: &mut Ui) {
world.resource_scope(|world, mut render_settings: Mut<WorldRenderSettings>| {
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<Assets<Image>>| {
update_textures(
world.resource::<WorldManager>(),
&render_settings,
&mut images,
);
});
}
}
});
}
fn name() -> &'static str {
"Overlay Selection"
}
}

View file

@ -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<Self>, ui: &mut Ui) {
world.resource_scope(|world, mut render_settings: Mut<WorldRenderSettings>| {
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<Assets<Image>>| {
update_textures(
world.resource::<WorldManager>(),
&render_settings,
&mut images,
);
});
}
}
});
}
fn name() -> &'static str {
"View Selection"
}
}

View file

@ -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<Image>>,
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<dyn std::error::Error>> {
})
.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)