use bevy::{
asset::RenderAssetUsages,
prelude::*,
render::{
extract_resource::{ExtractResource, ExtractResourcePlugin},
render_asset::RenderAssets,
render_resource::{Extent3d, TextureDimension, TextureFormat},
texture::GpuImage,
},
};
pub(crate) const NO_BED_SPAN: f32 = -1.0;
#[cfg_attr(not(test), allow(dead_code))]
pub const NO_BED_DEPTH: f32 = 256.0;
#[derive(Resource, Debug, Clone, ExtractResource)]
pub struct BedHeightMap {
pub image: Handle<Image>,
pub origin: Vec2,
pub size: Vec2,
pub height_range: [f32; 2],
}
impl BedHeightMap {
pub fn from_height_fn(
images: &mut Assets<Image>,
height: impl Fn(f32, f32) -> f32,
resolution: u32,
origin: Vec2,
step: f32,
) -> Self {
let mut range = [f32::MAX, f32::MIN];
let mut bytes = Vec::with_capacity((resolution * resolution) as usize);
let mut raw = Vec::with_capacity(bytes.capacity());
for row in 0..resolution {
for column in 0..resolution {
let value = height(
origin.x + column as f32 * step,
origin.y + row as f32 * step,
);
range[0] = range[0].min(value);
range[1] = range[1].max(value);
raw.push(value);
}
}
let span = (range[1] - range[0]).max(f32::MIN_POSITIVE);
for value in &raw {
bytes.push(
(((value - range[0]) / span) * 255.0)
.round()
.clamp(0.0, 255.0) as u8,
);
}
let image = Image::new(
Extent3d {
width: resolution,
height: resolution,
depth_or_array_layers: 1,
},
TextureDimension::D2,
bytes,
TextureFormat::R8Unorm,
RenderAssetUsages::MAIN_WORLD | RenderAssetUsages::RENDER_WORLD,
);
Self {
image: images.add(image),
origin,
size: Vec2::splat(step * (resolution - 1) as f32),
height_range: range,
}
}
}
#[derive(Resource, Debug, Clone, ExtractResource)]
pub struct GpuFallback(pub Handle<Image>);
impl FromWorld for GpuFallback {
fn from_world(world: &mut World) -> Self {
let mut images = world.resource_mut::<Assets<Image>>();
let image = Image::new_fill(
Extent3d::default(),
TextureDimension::D2,
&[0],
TextureFormat::R8Unorm,
RenderAssetUsages::default(),
);
Self(images.add(image))
}
}
pub fn gpu_image<'a>(
bed: Option<&BedHeightMap>,
fallback: &'a GpuFallback,
images: &'a RenderAssets<GpuImage>,
) -> Option<&'a GpuImage> {
let handle = bed
.map(|map| map.image.clone())
.unwrap_or_else(|| fallback.0.clone());
images.get(&handle)
}
pub fn add(app: &mut App) {
app.add_plugins(ExtractResourcePlugin::<BedHeightMap>::default());
app.init_resource::<GpuFallback>();
app.add_plugins(ExtractResourcePlugin::<GpuFallback>::default());
}