diff --git a/crates/bevy_render/src/texture/texture.rs b/crates/bevy_render/src/texture/texture.rs index 79d8499ec1d7e..7a71293e64fcf 100644 --- a/crates/bevy_render/src/texture/texture.rs +++ b/crates/bevy_render/src/texture/texture.rs @@ -12,20 +12,39 @@ use std::collections::HashSet; pub const TEXTURE_ASSET_INDEX: usize = 0; pub const SAMPLER_ASSET_INDEX: usize = 1; +#[derive(Default)] pub struct Texture { pub data: Vec, pub size: Vec2, } +const FORMAT_SIZE: usize = 4; // TODO: get this from an actual format type + impl Texture { pub fn new(data: Vec, size: Vec2) -> Self { Self { data, size } } + pub fn new_fill(size: Vec2, pixel: &[u8]) -> Self { + let mut value = Self::default(); + value.resize(size); + for current_pixel in value.data.chunks_exact_mut(pixel.len()) { + current_pixel.copy_from_slice(&pixel); + } + value + } + pub fn aspect(&self) -> f32 { self.size.y() / self.size.x() } + pub fn resize(&mut self, size: Vec2) { + self.size = size; + let width = size.x() as usize; + let height = size.y() as usize; + self.data.resize(width * height * FORMAT_SIZE, 0); + } + pub fn texture_resource_system( mut state: ResMut, render_resources: Res, diff --git a/crates/bevy_sprite/src/texture_atlas_builder.rs b/crates/bevy_sprite/src/texture_atlas_builder.rs index 65ba01543fa04..81ee2eb39b352 100644 --- a/crates/bevy_sprite/src/texture_atlas_builder.rs +++ b/crates/bevy_sprite/src/texture_atlas_builder.rs @@ -1,42 +1,89 @@ -use crate::{TextureAtlas, Rect}; +use crate::{Rect, TextureAtlas}; use bevy_asset::{Assets, Handle}; use bevy_render::texture::Texture; use glam::Vec2; -use guillotiere::{size2, Allocation, AtlasAllocator}; +use guillotiere::{size2, AllocId, Allocation, AtlasAllocator}; use std::collections::HashMap; pub struct TextureAtlasBuilder { - pub texture_allocations: Vec<(Handle, Allocation)>, + pub texture_allocations: HashMap, Allocation>, + pub allocation_textures: HashMap>, pub atlas_allocator: AtlasAllocator, - pub texture: Texture, + pub atlas_texture: Texture, + pub max_size: Vec2, } impl Default for TextureAtlasBuilder { fn default() -> Self { - Self::new(Vec2::new(256., 256.)) + Self::new(Vec2::new(256., 256.), Vec2::new(2048., 2048.)) } } const FORMAT_SIZE: usize = 4; // TODO: get this from an actual format type impl TextureAtlasBuilder { - pub fn new(initial_size: Vec2) -> Self { - let width = initial_size.x() as usize; - let height = initial_size.y() as usize; + pub fn new(initial_size: Vec2, max_size: Vec2) -> Self { Self { texture_allocations: Default::default(), - atlas_allocator: AtlasAllocator::new(size2(width as i32, height as i32)), - texture: Texture::new(vec![0; width * height * FORMAT_SIZE], initial_size), + allocation_textures: Default::default(), + atlas_allocator: AtlasAllocator::new(to_size2(initial_size)), + atlas_texture: Texture::new_fill(initial_size, &[0,0,0,0]), + max_size, } } - pub fn add_texture(&mut self, texture_handle: Handle, texture: &Texture) { - // TODO: resize if allocation fails - let allocation = self - .atlas_allocator - .allocate(size2(texture.size.x() as i32, texture.size.y() as i32)) - .unwrap(); + pub fn add_texture(&mut self, texture_handle: Handle, textures: &Assets) { + let texture = textures.get(&texture_handle).unwrap(); + let mut queued_textures= vec![texture_handle]; + loop { + let mut failed_textures = Vec::new(); + while let Some(texture_handle) = queued_textures.pop() { + let allocation = self + .atlas_allocator + .allocate(size2(texture.size.x() as i32, texture.size.y() as i32)); + if let Some(allocation) = allocation { + self.place_texture(allocation, texture_handle, texture); + } else { + failed_textures.push(texture_handle); + } + } + + if failed_textures.len() == 0 { + break; + } + + queued_textures = failed_textures; + + // if allocation failed, resize the atlas + let new_size = self.atlas_texture.size * 2.0; + if new_size > self.max_size { + panic!( + "Ran out of space in Atlas. This atlas cannot be larger than: {:?}", + self.max_size + ); + } + + let new_size2 = to_size2(new_size); + self.atlas_texture = Texture::new_fill(new_size, &[0,0,0,0]); + let change_list = self.atlas_allocator.resize_and_rearrange(new_size2); + + for change in change_list.changes { + if let Some(changed_texture_handle) = self.allocation_textures.remove(&change.old.id) { + self.texture_allocations.remove(&changed_texture_handle); + let changed_texture = textures.get(&changed_texture_handle).unwrap(); + self.place_texture(change.new, changed_texture_handle, changed_texture); + } + } + + for failure in change_list.failures { + let failed_texture = self.allocation_textures.remove(&failure.id).unwrap(); + queued_textures.push(failed_texture); + } + } + } + + fn place_texture(&mut self, allocation: Allocation, texture_handle: Handle, texture: &Texture) { let rect = allocation.rectangle; - let atlas_width = self.texture.size.x() as usize; + let atlas_width = self.atlas_texture.size.x() as usize; let rect_width = rect.width() as usize; for (texture_y, bound_y) in (rect.min.y..rect.max.y).map(|i| i as usize).enumerate() { @@ -44,15 +91,17 @@ impl TextureAtlasBuilder { let end = begin + rect_width * FORMAT_SIZE; let texture_begin = texture_y * rect_width * FORMAT_SIZE; let texture_end = texture_begin + rect_width * FORMAT_SIZE; - self.texture.data[begin..end] + self.atlas_texture.data[begin..end] .copy_from_slice(&texture.data[texture_begin..texture_end]); } - self.texture_allocations.push((texture_handle, allocation)); + + self.allocation_textures.insert(allocation.id, texture_handle); + self.texture_allocations.insert(texture_handle, allocation); } pub fn remove_texture(&mut self, texture_handle: Handle) { - if let Some(position) = self.texture_allocations.iter().position(|(handle, _)| *handle == texture_handle) { - let (_, allocation) = self.texture_allocations.remove(position); + if let Some(allocation) = self.texture_allocations.remove(&texture_handle) { + self.allocation_textures.remove(&allocation.id); self.atlas_allocator.deallocate(allocation.id); } } @@ -66,7 +115,7 @@ impl TextureAtlasBuilder { } TextureAtlas { dimensions: to_vec2(self.atlas_allocator.size()), - texture: textures.add(self.texture), + texture: textures.add(self.atlas_texture), textures: texture_rects, texture_handles: Some(texture_handles), } @@ -85,4 +134,7 @@ impl From for Rect { fn to_vec2(size: guillotiere::Size) -> Vec2 { Vec2::new(size.width as f32, size.height as f32) } - \ No newline at end of file + +fn to_size2(vec2: Vec2) -> guillotiere::Size { + guillotiere::Size::new(vec2.x() as i32, vec2.y() as i32) +} diff --git a/examples/2d/texture_atlas.rs b/examples/2d/texture_atlas.rs index 1e22d1af895be..0c70d7a9c20d3 100644 --- a/examples/2d/texture_atlas.rs +++ b/examples/2d/texture_atlas.rs @@ -42,37 +42,52 @@ fn load_atlas( asset_server: Res, mut texture_atlases: ResMut>, mut textures: ResMut>, + mut materials: ResMut>, ) { if state.atlas_loaded { return; } - let mut texture_atlas_builder = TextureAtlasBuilder::new(Vec2::new(600., 600.)); + let mut texture_atlas_builder = TextureAtlasBuilder::default(); if let Some(LoadState::Loaded(_)) = asset_server.get_group_load_state(&rpg_sprite_handles.handles) { // TODO: sort by size (within atlas builder) for texture_id in rpg_sprite_handles.handles.iter() { let handle = Handle::from_id(*texture_id); - let texture = textures.get(&handle).unwrap(); - texture_atlas_builder.add_texture(handle, texture); + texture_atlas_builder.add_texture(handle, &textures); } let texture_atlas = texture_atlas_builder.finish(&mut textures); + let texture_atlas_texture= texture_atlas.texture; let vendor_handle = asset_server .get_handle("assets/textures/rpg/chars/vendor/generic-rpg-vendor.png") .unwrap(); let vendor_index = texture_atlas.get_texture_index(vendor_handle).unwrap(); let atlas_handle = texture_atlases.add(texture_atlas); - command_buffer.build().add_entity(SpriteSheetEntity { - sprite: TextureAtlasSprite { - index: vendor_index as u32, - scale: 4.0, + command_buffer + .build() + // draw a sprite from the atlas + .add_entity(SpriteSheetEntity { + sprite: TextureAtlasSprite { + index: vendor_index as u32, + scale: 4.0, + position: Vec3::new(150.0, 0.0, 0.0), + ..Default::default() + }, + texture_atlas: atlas_handle, ..Default::default() - }, - texture_atlas: atlas_handle, - ..Default::default() - }); + }) + // draw the atlas itself + .add_entity(SpriteEntity { + material: materials.add(texture_atlas_texture.into()), + quad: Quad { + position: Vec2::new(-300.0, 0.), + ..Default::default() + }, + sprite: Sprite { scale: 0.65 }, + ..Default::default() + }); state.atlas_loaded = true; }