Move planet rendering from WorldManager to a trait
This commit is contained in:
parent
cc2bf6f535
commit
616a7d0697
15 changed files with 293 additions and 294 deletions
|
@ -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]
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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<BiomeType> 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,
|
||||
},
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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<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
|
||||
}
|
||||
}
|
|
@ -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<f32, CartesianError> {
|
||||
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,
|
||||
|
|
|
@ -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<Color> {
|
||||
// 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<u8> {
|
||||
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::<Vec<u8>>()
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<ToolbarButton> 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<ToolbarButton> for &'static str {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<&ToolbarButton> for &'static str {
|
||||
fn from(button: &ToolbarButton) -> Self {
|
||||
(*button).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ToolbarButton> for String {
|
||||
fn from(button: ToolbarButton) -> Self {
|
||||
<&'static str>::from(button).into()
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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"))]
|
||||
|
|
167
src/planet_renderer.rs
Normal file
167
src/planet_renderer.rs
Normal file
|
@ -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<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."
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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<u8>;
|
||||
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<u8> {
|
||||
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::<Vec<u8>>()
|
||||
})
|
||||
.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
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue