use bevy::{
prelude::*,
render::{render_asset::RenderAssetUsages, render_resource::TextureFormat},
};
use colorgrad::{Gradient, LinearGradient};
use image::{imageops::FilterType, DynamicImage, Pixel};
use serde::{Deserialize, Serialize};
use crate::{
noise::{generate_noise_map, Noise},
util::export_asset,
};
pub struct MapPlugin;
impl Plugin for MapPlugin {
fn build(&self, app: &mut App) {
app.add_systems(Update, generate_map);
}
}
#[derive(Component, Serialize, Deserialize)]
#[serde(default, rename_all = "camelCase")]
pub struct Map {
pub noise: Noise,
pub size: [u32; 2],
pub image_size: [u32; 2],
pub same_size: bool,
pub anti_aliasing: bool,
#[serde(skip)]
pub export: bool,
}
#[derive(Bundle, Default)]
pub struct MapBundle {
pub map: Map,
pub image_bundle: ImageNode,
}
impl Default for Map {
fn default() -> Self {
Self {
noise: Noise::default(),
size: [400; 2],
image_size: [400; 2],
same_size: true,
anti_aliasing: true,
export: false,
}
}
}
fn generate_map(mut images: ResMut<Assets<Image>>, mut query: Query<(&mut Map, &mut ImageNode)>) {
for (mut map, mut ui_image) in &mut query {
map.noise.size = map.size;
let noise_values = generate_noise_map(&map.noise);
let noise = &mut map.noise;
let mut colors: Vec<colorgrad::Color> = Vec::with_capacity(noise.regions.len());
let mut domain: Vec<f32> = Vec::with_capacity(noise.regions.len());
for region in &noise.regions {
colors.push(colorgrad::Color {
r: f32::from(region.color[0]) / 255.0,
g: f32::from(region.color[1]) / 255.0,
b: f32::from(region.color[2]) / 255.0,
a: f32::from(region.color[3]) / 255.0,
});
domain.push(region.position);
}
let grad = colorgrad::GradientBuilder::new()
.colors(&colors)
.domain(&domain)
.build::<LinearGradient>()
.unwrap_or_else(|_| {
colorgrad::GradientBuilder::new()
.colors(&colors)
.build::<LinearGradient>()
.expect("Gradient generation failed")
});
let mut gradient_buffer = image::ImageBuffer::from_pixel(
noise.gradient.size[0],
noise.gradient.size[1],
image::Rgba(noise.base_color),
);
for (x, _, pixel) in gradient_buffer.enumerate_pixels_mut() {
let rgba = grad
.at((f64::from(x) * 100.0 / f64::from(noise.gradient.size[0])) as f32)
.to_rgba8();
pixel.blend(&image::Rgba(rgba));
}
noise.gradient.image = images.add(
Image::from_dynamic(
gradient_buffer.into(),
true,
RenderAssetUsages::RENDER_WORLD,
)
.convert(bevy::render::render_resource::TextureFormat::Rgba8UnormSrgb)
.expect("Could not convert to Rgba8UnormSrgb"),
);
let mut image_buffer = image::ImageBuffer::from_pixel(
noise.size[0],
noise.size[1],
image::Rgba(noise.base_color),
);
for (x, y, pixel) in image_buffer.enumerate_pixels_mut() {
let height = noise_values[x as usize][y as usize];
let target_color = grad.at(height as f32).to_rgba8();
pixel.blend(&image::Rgba(target_color));
}
if !map.same_size {
image_buffer = DynamicImage::from(image_buffer.clone())
.resize_exact(
map.image_size[0],
map.image_size[1],
if map.anti_aliasing {
FilterType::Triangle
} else {
FilterType::Nearest
},
)
.to_rgba8();
}
if map.export {
export_asset(image_buffer.clone());
map.export = false;
}
let map_texture =
Image::from_dynamic(image_buffer.into(), true, RenderAssetUsages::RENDER_WORLD)
.convert(TextureFormat::Rgba8UnormSrgb)
.expect("Could not convert to Rgba8UnormSrgb");
ui_image.image = images.add(map_texture);
}
}