Move planet rendering from WorldManager to a trait

This commit is contained in:
Tobias Berger 2022-11-13 11:26:50 +01:00
parent cc2bf6f535
commit 616a7d0697
Signed by: toby
GPG key ID: 2D05EFAB764D6A88
15 changed files with 293 additions and 294 deletions

View file

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

View file

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

View file

@ -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,
},

View file

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

View file

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

View file

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

View file

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

View file

@ -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()
}
}

View file

@ -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()

View file

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

View file

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

View file

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

View file

@ -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
View 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
}
}