diff --git a/rustfmt.toml b/.rustfmt.toml similarity index 100% rename from rustfmt.toml rename to .rustfmt.toml diff --git a/Cargo.toml b/Cargo.toml index d495a24..f7a09c4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,7 +27,7 @@ codegen-units = 1 # bevy/trace_chrome for tracing by function # https://github.com/bevyengine/bevy/blob/main/docs/profiling.md logging = ["planet/logging"] -render = ["bevy/bevy_asset", "bevy/bevy_winit", "bevy/x11", "bevy/wayland", "bevy/render", "planet/render", "dep:fxhash", "dep:bevy_egui", "dep:tinyfiledialogs"] +render = ["bevy/bevy_asset", "bevy/bevy_winit", "bevy/x11", "bevy/wayland", "bevy/render", "dep:fxhash", "dep:bevy_egui", "dep:tinyfiledialogs"] default = ["render", "logging"] [dependencies.planet] diff --git a/planet/Cargo.toml b/planet/Cargo.toml index ef33a8a..16bb3ff 100644 --- a/planet/Cargo.toml +++ b/planet/Cargo.toml @@ -8,8 +8,7 @@ release = { strip = "symbols", lto = "thin", opt-level = "z" } [features] logging = [] -render = ["bevy/render"] -default = ["render", "logging"] +default = ["logging"] [dependencies.rand] version = "0.8.5" diff --git a/planet/src/biome.rs b/planet/src/biome.rs index 003ed85..932cc68 100644 --- a/planet/src/biome.rs +++ b/planet/src/biome.rs @@ -1,11 +1,11 @@ -use crate::{macros::iterable_enum, World}; -#[cfg(feature = "render")] -use bevy::render::color::Color; +use { + crate::{macros::iterable_enum, World}, + bevy::render::color::Color, +}; #[derive(Debug, Clone, Default)] pub struct BiomeStats { pub name: String, - #[cfg(feature = "render")] pub color: Color, pub min_altitude: f32, pub max_altitude: f32, @@ -30,90 +30,82 @@ impl From for BiomeStats { fn from(biome_type: BiomeType) -> BiomeStats { match biome_type { BiomeType::IceCap => BiomeStats { - name: "Ice Cap".into(), - #[cfg(feature = "render")] - color: Color::rgb_u8(253, 244, 235), - min_altitude: World::MIN_ALTITUDE, - max_altitude: World::MAX_ALTITUDE, - min_rainfall: World::MIN_RAINFALL, - max_rainfall: World::MAX_RAINFALL, + name: "Ice Cap".into(), + color: Color::rgb_u8(253, 244, 235), + min_altitude: World::MIN_ALTITUDE, + max_altitude: World::MAX_ALTITUDE, + min_rainfall: World::MIN_RAINFALL, + max_rainfall: World::MAX_RAINFALL, min_temperature: World::MIN_TEMPERATURE, max_temperature: -15.0, }, BiomeType::Ocean => BiomeStats { - name: "Ocean".into(), - #[cfg(feature = "render")] - color: Color::rgb_u8(28, 66, 84), - min_altitude: World::MIN_ALTITUDE, - max_altitude: 0.0, - min_rainfall: World::MIN_RAINFALL, - max_rainfall: World::MAX_RAINFALL, + name: "Ocean".into(), + color: Color::rgb_u8(28, 66, 84), + min_altitude: World::MIN_ALTITUDE, + max_altitude: 0.0, + min_rainfall: World::MIN_RAINFALL, + max_rainfall: World::MAX_RAINFALL, min_temperature: -15.0, max_temperature: World::MAX_TEMPERATURE, }, BiomeType::Grassland => BiomeStats { - name: "Grassland".into(), - #[cfg(feature = "render")] - color: Color::rgb_u8(167, 177, 84), - min_altitude: 0.0, - max_altitude: World::MAX_ALTITUDE, - min_rainfall: 25.0, - max_rainfall: 1475.0, + name: "Grassland".into(), + color: Color::rgb_u8(167, 177, 84), + min_altitude: 0.0, + max_altitude: World::MAX_ALTITUDE, + min_rainfall: 25.0, + max_rainfall: 1475.0, min_temperature: -5.0, max_temperature: World::MAX_TEMPERATURE, }, BiomeType::Forest => BiomeStats { - name: "Forest".into(), - #[cfg(feature = "render")] - color: Color::rgb_u8(76, 132, 55), - min_altitude: 0.0, - max_altitude: World::MAX_ALTITUDE, - min_rainfall: 975.0, - max_rainfall: 2475.0, + name: "Forest".into(), + color: Color::rgb_u8(76, 132, 55), + min_altitude: 0.0, + max_altitude: World::MAX_ALTITUDE, + min_rainfall: 975.0, + max_rainfall: 2475.0, min_temperature: -5.0, max_temperature: World::MAX_TEMPERATURE, }, BiomeType::Taiga => BiomeStats { - name: "Taiga".into(), - #[cfg(feature = "render")] - color: Color::rgb_u8(43, 63, 40), - min_altitude: 0.0, - max_altitude: World::MAX_ALTITUDE, - min_rainfall: 475.0, - max_rainfall: World::MAX_RAINFALL, + name: "Taiga".into(), + color: Color::rgb_u8(43, 63, 40), + min_altitude: 0.0, + max_altitude: World::MAX_ALTITUDE, + min_rainfall: 475.0, + max_rainfall: World::MAX_RAINFALL, min_temperature: -15.0, max_temperature: -0.0, }, BiomeType::Tundra => BiomeStats { - name: "Tundra ".into(), - #[cfg(feature = "render")] - color: Color::rgb_u8(139, 139, 128), - min_altitude: 0.0, - max_altitude: World::MAX_ALTITUDE, - min_rainfall: World::MIN_RAINFALL, - max_rainfall: 725.0, + name: "Tundra ".into(), + color: Color::rgb_u8(139, 139, 128), + min_altitude: 0.0, + max_altitude: World::MAX_ALTITUDE, + min_rainfall: World::MIN_RAINFALL, + max_rainfall: 725.0, min_temperature: -20.0, max_temperature: -0.0, }, BiomeType::Desert => BiomeStats { - name: "Desert ".into(), - #[cfg(feature = "render")] - color: Color::rgb_u8(253, 225, 171), - min_altitude: 0.0, - max_altitude: World::MAX_ALTITUDE, - min_rainfall: World::MIN_RAINFALL, - max_rainfall: 125.0, + name: "Desert ".into(), + color: Color::rgb_u8(253, 225, 171), + min_altitude: 0.0, + max_altitude: World::MAX_ALTITUDE, + min_rainfall: World::MIN_RAINFALL, + max_rainfall: 125.0, min_temperature: -5.0, max_temperature: World::MAX_TEMPERATURE, }, BiomeType::Rainforest => BiomeStats { - name: "Rainforest".into(), - #[cfg(feature = "render")] - color: Color::rgb_u8(59, 103, 43), - min_altitude: 0.0, - max_altitude: World::MAX_ALTITUDE, - min_rainfall: 1975.0, - max_rainfall: World::MAX_RAINFALL, + name: "Rainforest".into(), + color: Color::rgb_u8(59, 103, 43), + min_altitude: 0.0, + max_altitude: World::MAX_ALTITUDE, + min_rainfall: 1975.0, + max_rainfall: World::MAX_RAINFALL, min_temperature: -5.0, max_temperature: World::MAX_TEMPERATURE, }, diff --git a/planet/src/lib.rs b/planet/src/lib.rs index 3852e83..31c0daa 100644 --- a/planet/src/lib.rs +++ b/planet/src/lib.rs @@ -41,8 +41,4 @@ 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/perlin.rs b/planet/src/perlin.rs index 6899b6b..23d8903 100644 --- a/planet/src/perlin.rs +++ b/planet/src/perlin.rs @@ -14,7 +14,8 @@ const PERMUTATION: [u8; 256] = [ 128, 195, 78, 66, 215, 61, 156, 180, ]; -pub fn get_value(x: f32, y: f32, z: f32) -> f32 { +#[must_use] +pub fn perlin_value(x: f32, y: f32, z: f32) -> f32 { let p = [PERMUTATION, PERMUTATION].concat(); let fx: i32 = f32::floor(x) as i32; @@ -68,10 +69,12 @@ pub fn get_value(x: f32, y: f32, z: f32) -> f32 { )) } +#[must_use] fn fade(t: f32) -> f32 { t * t * t * (t * (t * 6.0 - 15.0) + 10.0) } +#[must_use] fn grad(hash: u8, x: f32, y: f32, z: f32) -> f32 { let h = hash & 15; let u = if h < 8 { x } else { y }; @@ -86,10 +89,12 @@ fn grad(hash: u8, x: f32, y: f32, z: f32) -> f32 { (if h & 1 == 0 { u } else { -u }) + (if h & 2 == 0 { v } else { -v }) } +#[must_use] fn lerp(t: f32, a: f32, b: f32) -> f32 { a + t * (b - a) } +#[must_use] fn scale(n: f32) -> f32 { (1.0 + n) / 2.0 } diff --git a/planet/src/rendering/mod.rs b/planet/src/rendering/mod.rs deleted file mode 100644 index 56bc96c..0000000 --- a/planet/src/rendering/mod.rs +++ /dev/null @@ -1,45 +0,0 @@ -#[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.rs b/planet/src/world.rs index b4d5b3a..ace7375 100644 --- a/planet/src/world.rs +++ b/planet/src/world.rs @@ -106,27 +106,27 @@ pub struct TerrainCell { } impl World { - pub(crate) const ALTITUDE_SPAN: f32 = World::MAX_ALTITUDE - World::MIN_ALTITUDE; - const CONTINENT_MAX_SIZE_FACTOR: f32 = 6.0; - const CONTINENT_MIN_SIZE_FACTOR: f32 = 2.5; - pub(crate) const MAX_ALTITUDE: f32 = 15000.0; - pub(crate) const MAX_RAINFALL: f32 = 7500.0; - pub(crate) const MAX_TEMPERATURE: f32 = 30.0; - pub(crate) const MIN_ALTITUDE: f32 = -15000.0; - pub(crate) const MIN_RAINFALL: f32 = 0.0; - pub(crate) const MIN_TEMPERATURE: f32 = -35.0; - const MOUNTAIN_RANGE_MIX_FACTOR: f32 = 0.075; - const MOUNTAIN_RANGE_WIDTH_FACTOR: f32 = 25.0; - const NUM_CONTINENTS: u8 = 7; - const RAINFALL_DRYNESS_FACTOR: f32 = 0.005; - const RAINFALL_DRYNESS_OFFSET: f32 = World::RAINFALL_DRYNESS_FACTOR * World::MAX_RAINFALL; - const RAINFALL_SPAN: f32 = World::MAX_RAINFALL - World::MIN_RAINFALL; - const TEMPERATURE_ALTITUDE_FACTOR: f32 = 1.0; - const TEMPERATURE_SPAN: f32 = World::MAX_TEMPERATURE - World::MIN_TEMPERATURE; - const TERRAIN_NOISE_FACTOR_1: f32 = 0.15; - const TERRAIN_NOISE_FACTOR_2: f32 = 0.15; - const TERRAIN_NOISE_FACTOR_3: f32 = 0.1; - const TERRAIN_NOISE_FACTOR_4: f32 = 2.5; + pub const ALTITUDE_SPAN: f32 = World::MAX_ALTITUDE - World::MIN_ALTITUDE; + pub const CONTINENT_MAX_SIZE_FACTOR: f32 = 6.0; + pub const CONTINENT_MIN_SIZE_FACTOR: f32 = 2.5; + pub const MAX_ALTITUDE: f32 = 15000.0; + pub const MAX_RAINFALL: f32 = 7500.0; + pub const MAX_TEMPERATURE: f32 = 30.0; + pub const MIN_ALTITUDE: f32 = -15000.0; + pub const MIN_RAINFALL: f32 = 0.0; + pub const MIN_TEMPERATURE: f32 = -35.0; + pub const MOUNTAIN_RANGE_MIX_FACTOR: f32 = 0.075; + pub const MOUNTAIN_RANGE_WIDTH_FACTOR: f32 = 25.0; + pub const NUM_CONTINENTS: u8 = 7; + pub const RAINFALL_DRYNESS_FACTOR: f32 = 0.005; + pub const RAINFALL_DRYNESS_OFFSET: f32 = World::RAINFALL_DRYNESS_FACTOR * World::MAX_RAINFALL; + pub const RAINFALL_SPAN: f32 = World::MAX_RAINFALL - World::MIN_RAINFALL; + pub const TEMPERATURE_ALTITUDE_FACTOR: f32 = 1.0; + pub const TEMPERATURE_SPAN: f32 = World::MAX_TEMPERATURE - World::MIN_TEMPERATURE; + pub const TERRAIN_NOISE_FACTOR_1: 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_4: f32 = 2.5; pub fn new(width: u32, height: u32, seed: u32) -> World { World { @@ -326,7 +326,7 @@ impl World { offset: Vec3A, ) -> Result { let cartesian = cartesian_coordinates(alpha, beta, radius)?; - Ok(perlin::get_value( + Ok(perlin::perlin_value( cartesian.x + offset.x, cartesian.y + offset.y, cartesian.z + offset.z, diff --git a/planet/src/world_manager.rs b/planet/src/world_manager.rs index 1b374f9..b7362f7 100644 --- a/planet/src/world_manager.rs +++ b/planet/src/world_manager.rs @@ -159,6 +159,7 @@ impl WorldManager { self.world.as_ref() } + #[must_use] pub fn world(&self) -> &World { assert!(self.world.is_some(), "No world."); self.get_world().unwrap() @@ -171,149 +172,4 @@ impl WorldManager { self.world = Some(new_world); Ok(self.get_world().unwrap()) } - - #[cfg(feature = "render")] - #[must_use] - fn generate_color(&self, cell: &TerrainCell, render_settings: &WorldRenderSettings) -> Color { - let base_color = match render_settings.view { - WorldView::Biomes => self.biome_color(cell), - WorldView::Topography => WorldManager::altitude_contour_color(cell.altitude), - }; - let mut normalizer = 1.0; - - let mut red = base_color.r(); - let mut green = base_color.g(); - let mut blue = base_color.b(); - - if render_settings.overlay_visible(&WorldOverlay::Rainfall) { - normalizer += 1.0; - let rainfall_color = self.rainfall_contour_color(cell.rainfall); - - red += rainfall_color.r(); - green += rainfall_color.g(); - blue += rainfall_color.b(); - } - - if render_settings.overlay_visible(&WorldOverlay::Temperature) { - normalizer += 1.0; - let temperature_color = self.temperature_contour_color(cell.temperature); - - red += temperature_color.r(); - green += temperature_color.g(); - blue += temperature_color.b(); - } - - Color::rgb(red / normalizer, green / normalizer, blue / normalizer) - } - - // #[cfg(feature = "render")] - // #[must_use] - // fn altitude_color(altitude: f32) -> Color { - // if altitude < 0.0 { - // Color::rgb(0.0, 0.0, (2.0 - altitude / World::MIN_ALTITUDE) / 2.0) - // } else { - // let mult = (1.0 + altitude / World::MAX_ALTITUDE) / 2.0; - - // Color::rgb(0.58 * mult, 0.29 * mult, 0.0) - // } - // } - - #[cfg(feature = "render")] - #[must_use] - fn altitude_contour_color(altitude: f32) -> Color { - if altitude < 0.0 { - Color::rgb(0.0, 0.0, (2.0 - altitude / World::MIN_ALTITUDE) / 2.0) - } else { - let mut shade_value = 1.0; - - while shade_value > altitude / World::MAX_ALTITUDE { - shade_value -= 0.05; - } - - Color::rgb(shade_value, shade_value, shade_value) - } - } - - #[cfg(feature = "render")] - #[must_use] - fn rainfall_contour_color(&self, rainfall: f32) -> Color { - let mut shade_value = 1.0; - let value = rainfall / self.world().max_rainfall; - - while shade_value > value { - shade_value -= 0.1; - } - - Color::rgb(0.0, shade_value, 0.0) - } - - #[cfg(feature = "render")] - #[must_use] - fn temperature_contour_color(&self, temperature: f32) -> Color { - let mut shade_value = 1.0; - let value = (temperature - self.world().min_temperature) - / (self.world().max_temperature - self.world().min_temperature); - - while shade_value > value { - shade_value -= 0.1; - } - - Color::rgb(shade_value, 0.0, 1.0 - shade_value) - } - - #[cfg(feature = "render")] - #[must_use] - fn biome_color(&self, cell: &TerrainCell) -> Color { - let slant = self.world().get_slant(cell); - - let slant_factor = f32::min(1.0, (4.0 + (10.0 * slant / World::ALTITUDE_SPAN)) / 5.0); - let altitude_factor = f32::min( - 1.0, - (0.5 + (cell.altitude - World::MIN_ALTITUDE) / World::ALTITUDE_SPAN) / 1.5, - ); - - let mut red = 0.0; - let mut green = 0.0; - let mut blue = 0.0; - - for (biome, presence) in cell.biome_presences.iter() { - red += BiomeStats::from(biome).color.r() * presence; - green += BiomeStats::from(biome).color.g() * presence; - blue += BiomeStats::from(biome).color.b() * presence; - } - red *= slant_factor * altitude_factor; - green *= slant_factor * altitude_factor; - blue *= slant_factor * altitude_factor; - Color::rgb(red, green, blue) - } - - // #[cfg(feature = "render")] - // #[must_use] - // fn map_colors(&self) -> Vec { - // self.world() - // .terrain - // .iter() - // .rev() - // .flatten() - // .map(|cell| self.generate_color(cell)) - // .collect() - // } - - #[cfg(feature = "render")] - #[must_use] - pub fn map_color_bytes(&self, render_settings: &WorldRenderSettings) -> Vec { - self.world() - .terrain - .iter() - .rev() - .flatten() - .flat_map(|cell| { - self.generate_color(cell, render_settings) - .as_rgba_f32() - .iter() - .flat_map(|num| num.to_le_bytes()) - .collect::>() - }) - .collect() - } } diff --git a/src/gui/widgets/toolbar.rs b/src/gui/widgets/toolbar.rs index 2ecf3e4..3b05e35 100644 --- a/src/gui/widgets/toolbar.rs +++ b/src/gui/widgets/toolbar.rs @@ -12,7 +12,6 @@ use { bevy::{ ecs::{ change_detection::Mut, - component::Component, system::{SystemParam, SystemState}, world::World, }, @@ -59,6 +58,12 @@ impl ToolbarButton { impl From for &'static str { fn from(button: ToolbarButton) -> Self { + (&button).into() + } +} + +impl From<&ToolbarButton> for &'static str { + fn from(button: &ToolbarButton) -> Self { match button { ToolbarButton::Views => "Change view", ToolbarButton::Overlays => "Overlays", @@ -68,12 +73,6 @@ impl From for &'static str { } } -impl From<&ToolbarButton> for &'static str { - fn from(button: &ToolbarButton) -> Self { - (*button).into() - } -} - impl From for String { fn from(button: ToolbarButton) -> Self { <&'static str>::from(button).into() diff --git a/src/gui/windows/world_overlay_selection.rs b/src/gui/windows/world_overlay_selection.rs index 0b17169..7e14ad6 100644 --- a/src/gui/windows/world_overlay_selection.rs +++ b/src/gui/windows/world_overlay_selection.rs @@ -1,12 +1,15 @@ use { - crate::{gui::WindowSystem, resources::ShouldRedraw}, + crate::{ + gui::WindowSystem, + planet_renderer::{WorldOverlay, WorldRenderSettings}, + resources::ShouldRedraw, + }, bevy::ecs::{ change_detection::Mut, system::{SystemParam, SystemState}, world::World, }, bevy_egui::egui::Ui, - planet::{WorldOverlay, WorldRenderSettings}, std::marker::PhantomData, }; diff --git a/src/gui/windows/world_view_selection.rs b/src/gui/windows/world_view_selection.rs index 816aa3f..6ed9a60 100644 --- a/src/gui/windows/world_view_selection.rs +++ b/src/gui/windows/world_view_selection.rs @@ -1,12 +1,15 @@ use { - crate::{gui::WindowSystem, resources::ShouldRedraw}, + crate::{ + gui::WindowSystem, + planet_renderer::{WorldRenderSettings, WorldView}, + resources::ShouldRedraw, + }, bevy::ecs::{ change_detection::Mut, system::{SystemParam, SystemState}, world::World, }, bevy_egui::egui::Ui, - planet::{WorldRenderSettings, WorldView}, std::marker::PhantomData, }; diff --git a/src/macros.rs b/src/macros.rs index 7b4bd03..700de28 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -2,12 +2,13 @@ macro_rules! iterable_enum { ($Name:ident { $($Variant:ident),*$(,)? }) => { - #[derive(Debug, Copy, Clone, Eq, PartialEq, Component)] + #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, bevy::ecs::component::Component)] pub enum $Name { $($Variant),*, } impl $Name { - const ITEMS: &'static [$Name] = &[$($Name::$Variant),*]; + pub const ITEMS: &'static [$Name] = &[$($Name::$Variant),*]; + pub const ITEM_COUNT: usize = $Name::ITEMS.len(); pub fn iterator() -> core::slice::Iter<'static, $Name> { $Name::ITEMS.iter() @@ -15,5 +16,26 @@ macro_rules! iterable_enum { } } } +macro_rules! iterable_enum_stringify { + ($Name:ident { $($Variant:ident),*$(,)? }) => + { + crate::macros::iterable_enum!($Name { $($Variant,)* }); + 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),)* + } + } + } + } +} #[cfg(feature = "render")] pub(crate) use iterable_enum; +pub(crate) use iterable_enum_stringify; diff --git a/src/main.rs b/src/main.rs index 5e244a0..6c3dc4e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,6 +4,8 @@ pub(crate) mod components; #[cfg(feature = "render")] pub(crate) mod gui; pub(crate) mod macros; +#[cfg(feature = "render")] +pub(crate) mod planet_renderer; pub(crate) mod plugins; #[cfg(feature = "render")] pub(crate) mod resources; @@ -52,7 +54,7 @@ use { }, components::panning::Pan2d, gui::{render_windows, widget, widgets::ToolbarWidget, window::open_window, windows::TileInfo}, - planet::WorldRenderSettings, + planet_renderer::{WorldRenderSettings, WorldRenderer}, resources::{CursorMapPosition, OpenedWindows, ShouldRedraw}, }; #[cfg(all(feature = "render", feature = "logging"))] diff --git a/src/planet_renderer.rs b/src/planet_renderer.rs new file mode 100644 index 0000000..ceee40c --- /dev/null +++ b/src/planet_renderer.rs @@ -0,0 +1,167 @@ +use { + crate::macros::iterable_enum_stringify, + bevy::{asset::HandleId, prelude::Color, utils::HashSet}, + planet::{BiomeStats, TerrainCell, World, WorldManager}, +}; + +iterable_enum_stringify!(WorldView { Biomes, Topography }); +iterable_enum_stringify!(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." + ); + } + } +} + +#[must_use] +fn altitude_contour_color(altitude: f32) -> Color { + if altitude < 0.0 { + Color::rgb(0.0, 0.0, (2.0 - altitude / World::MIN_ALTITUDE) / 2.0) + } else { + let mut shade_value = 1.0; + + while shade_value > altitude / World::MAX_ALTITUDE { + shade_value -= 0.05; + } + + Color::rgb(shade_value, shade_value, shade_value) + } +} + +#[must_use] +fn rainfall_contour_color(world: &World, rainfall: f32) -> Color { + let mut shade_value = 1.0; + let value = rainfall / world.max_rainfall; + + while shade_value > value { + shade_value -= 0.1; + } + + Color::rgb(0.0, shade_value, 0.0) +} + +#[must_use] +fn temperature_contour_color(world: &World, temperature: f32) -> Color { + let mut shade_value = 1.0; + let value = + (temperature - world.min_temperature) / (world.max_temperature - world.min_temperature); + + while shade_value > value { + shade_value -= 0.1; + } + + Color::rgb(shade_value, 0.0, 1.0 - shade_value) +} + +#[must_use] +fn biome_color(world: &World, cell: &TerrainCell) -> Color { + let slant = world.get_slant(cell); + + let slant_factor = f32::min(1.0, (4.0 + (10.0 * slant / World::ALTITUDE_SPAN)) / 5.0); + let altitude_factor = f32::min( + 1.0, + (0.5 + (cell.altitude - World::MIN_ALTITUDE) / World::ALTITUDE_SPAN) / 1.5, + ); + + let mut red = 0.0; + let mut green = 0.0; + let mut blue = 0.0; + + for (biome, presence) in cell.biome_presences.iter() { + red += BiomeStats::from(biome).color.r() * presence; + green += BiomeStats::from(biome).color.g() * presence; + blue += BiomeStats::from(biome).color.b() * presence; + } + red *= slant_factor * altitude_factor; + green *= slant_factor * altitude_factor; + blue *= slant_factor * altitude_factor; + Color::rgb(red, green, blue) +} +pub(crate) trait WorldRenderer { + fn map_color_bytes(&self, render_settings: &WorldRenderSettings) -> Vec; + fn generate_color(&self, cell: &TerrainCell, render_settings: &WorldRenderSettings) -> Color; +} +impl WorldRenderer for WorldManager { + #[must_use] + fn map_color_bytes(&self, render_settings: &WorldRenderSettings) -> Vec { + self.world() + .terrain + .iter() + .rev() + .flatten() + .flat_map(|cell| { + self.generate_color(cell, render_settings) + .as_rgba_f32() + .iter() + .flat_map(|num| num.to_le_bytes()) + .collect::>() + }) + .collect() + } + + #[must_use] + fn generate_color(&self, cell: &TerrainCell, render_settings: &WorldRenderSettings) -> Color { + let base_color = match render_settings.view { + WorldView::Biomes => biome_color(&self.world(), cell), + WorldView::Topography => altitude_contour_color(cell.altitude), + }; + let mut normalizer = 1.0; + + let mut red = base_color.r(); + let mut green = base_color.g(); + let mut blue = base_color.b(); + + if render_settings.overlay_visible(&WorldOverlay::Rainfall) { + normalizer += 1.0; + let rainfall_color = rainfall_contour_color(self.world(), cell.rainfall); + + red += rainfall_color.r(); + green += rainfall_color.g(); + blue += rainfall_color.b(); + } + + if render_settings.overlay_visible(&WorldOverlay::Temperature) { + normalizer += 1.0; + let temperature_color = temperature_contour_color(self.world(), cell.temperature); + + red += temperature_color.r(); + green += temperature_color.g(); + blue += temperature_color.b(); + } + + Color::rgb(red / normalizer, green / normalizer, blue / normalizer) + } +} + +impl Default for WorldView { + fn default() -> Self { + WorldView::Biomes + } +}