Add sprite atlases into the new renderer. (#2560)
# Objective Restore the functionality of sprite atlases in the new renderer. ### **Note:** This PR relies on #2555 ## Solution Mostly just a copy paste of the existing sprite atlas implementation, however I unified the rendering between sprites and atlases. Co-authored-by: Carter Anderson <mcanders1@gmail.com>
This commit is contained in:
parent
ae4f809a52
commit
115b170d1f
18
Cargo.toml
18
Cargo.toml
@ -42,7 +42,13 @@ default = [
|
|||||||
dynamic = ["bevy_dylib"]
|
dynamic = ["bevy_dylib"]
|
||||||
|
|
||||||
# Rendering support (Also needs the bevy_wgpu feature or a third-party rendering backend)
|
# Rendering support (Also needs the bevy_wgpu feature or a third-party rendering backend)
|
||||||
render = ["bevy_internal/bevy_pbr", "bevy_internal/bevy_render", "bevy_internal/bevy_sprite", "bevy_internal/bevy_text", "bevy_internal/bevy_ui"]
|
render = [
|
||||||
|
"bevy_internal/bevy_pbr",
|
||||||
|
"bevy_internal/bevy_render",
|
||||||
|
"bevy_internal/bevy_sprite",
|
||||||
|
"bevy_internal/bevy_text",
|
||||||
|
"bevy_internal/bevy_ui",
|
||||||
|
]
|
||||||
|
|
||||||
# Optional bevy crates
|
# Optional bevy crates
|
||||||
bevy_audio = ["bevy_internal/bevy_audio"]
|
bevy_audio = ["bevy_internal/bevy_audio"]
|
||||||
@ -92,14 +98,14 @@ subpixel_glyph_atlas = ["bevy_internal/subpixel_glyph_atlas"]
|
|||||||
bevy_ci_testing = ["bevy_internal/bevy_ci_testing"]
|
bevy_ci_testing = ["bevy_internal/bevy_ci_testing"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bevy_dylib = {path = "crates/bevy_dylib", version = "0.5.0", default-features = false, optional = true}
|
bevy_dylib = { path = "crates/bevy_dylib", version = "0.5.0", default-features = false, optional = true }
|
||||||
bevy_internal = {path = "crates/bevy_internal", version = "0.5.0", default-features = false}
|
bevy_internal = { path = "crates/bevy_internal", version = "0.5.0", default-features = false }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
anyhow = "1.0.4"
|
anyhow = "1.0.4"
|
||||||
rand = "0.8.0"
|
rand = "0.8.0"
|
||||||
ron = "0.6.2"
|
ron = "0.6.2"
|
||||||
serde = {version = "1", features = ["derive"]}
|
serde = { version = "1", features = ["derive"] }
|
||||||
# Needed to poll Task examples
|
# Needed to poll Task examples
|
||||||
futures-lite = "1.11.3"
|
futures-lite = "1.11.3"
|
||||||
|
|
||||||
@ -140,6 +146,10 @@ path = "examples/2d/text2d.rs"
|
|||||||
name = "texture_atlas"
|
name = "texture_atlas"
|
||||||
path = "examples/2d/texture_atlas.rs"
|
path = "examples/2d/texture_atlas.rs"
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "pipelined_texture_atlas"
|
||||||
|
path = "examples/2d/pipelined_texture_atlas.rs"
|
||||||
|
|
||||||
# 3D Rendering
|
# 3D Rendering
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "3d_scene"
|
name = "3d_scene"
|
||||||
|
98
examples/2d/pipelined_texture_atlas.rs
Normal file
98
examples/2d/pipelined_texture_atlas.rs
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
use bevy::{
|
||||||
|
asset::LoadState,
|
||||||
|
math::{Vec2, Vec3},
|
||||||
|
prelude::{
|
||||||
|
App, AssetServer, Assets, Commands, HandleUntyped, IntoSystem, Res, ResMut, State,
|
||||||
|
SystemSet, Transform,
|
||||||
|
},
|
||||||
|
render2::{camera::OrthographicCameraBundle, texture::Image},
|
||||||
|
sprite2::{
|
||||||
|
PipelinedSpriteBundle, PipelinedSpriteSheetBundle, Sprite, TextureAtlas,
|
||||||
|
TextureAtlasBuilder, TextureAtlasSprite,
|
||||||
|
},
|
||||||
|
PipelinedDefaultPlugins,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// In this example we generate a new texture atlas (sprite sheet) from a folder containing
|
||||||
|
/// individual sprites
|
||||||
|
fn main() {
|
||||||
|
App::new()
|
||||||
|
.init_resource::<RpgSpriteHandles>()
|
||||||
|
.add_plugins(PipelinedDefaultPlugins)
|
||||||
|
.add_state(AppState::Setup)
|
||||||
|
.add_system_set(SystemSet::on_enter(AppState::Setup).with_system(load_textures.system()))
|
||||||
|
.add_system_set(SystemSet::on_update(AppState::Setup).with_system(check_textures.system()))
|
||||||
|
.add_system_set(SystemSet::on_enter(AppState::Finished).with_system(setup.system()))
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
|
enum AppState {
|
||||||
|
Setup,
|
||||||
|
Finished,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct RpgSpriteHandles {
|
||||||
|
handles: Vec<HandleUntyped>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_textures(mut rpg_sprite_handles: ResMut<RpgSpriteHandles>, asset_server: Res<AssetServer>) {
|
||||||
|
rpg_sprite_handles.handles = asset_server.load_folder("textures/rpg").unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_textures(
|
||||||
|
mut state: ResMut<State<AppState>>,
|
||||||
|
rpg_sprite_handles: ResMut<RpgSpriteHandles>,
|
||||||
|
asset_server: Res<AssetServer>,
|
||||||
|
) {
|
||||||
|
if let LoadState::Loaded =
|
||||||
|
asset_server.get_group_load_state(rpg_sprite_handles.handles.iter().map(|handle| handle.id))
|
||||||
|
{
|
||||||
|
state.set(AppState::Finished).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup(
|
||||||
|
mut commands: Commands,
|
||||||
|
rpg_sprite_handles: Res<RpgSpriteHandles>,
|
||||||
|
asset_server: Res<AssetServer>,
|
||||||
|
mut texture_atlases: ResMut<Assets<TextureAtlas>>,
|
||||||
|
mut textures: ResMut<Assets<Image>>,
|
||||||
|
) {
|
||||||
|
let mut texture_atlas_builder = TextureAtlasBuilder::default();
|
||||||
|
for handle in rpg_sprite_handles.handles.iter() {
|
||||||
|
let texture = textures.get(handle).unwrap();
|
||||||
|
texture_atlas_builder.add_texture(handle.clone_weak().typed::<Image>(), texture);
|
||||||
|
}
|
||||||
|
|
||||||
|
let texture_atlas = texture_atlas_builder.finish(&mut textures).unwrap();
|
||||||
|
let texture_atlas_texture = texture_atlas.texture.clone();
|
||||||
|
let vendor_handle = asset_server.get_handle("textures/rpg/chars/vendor/generic-rpg-vendor.png");
|
||||||
|
let vendor_index = texture_atlas.get_texture_index(&vendor_handle).unwrap();
|
||||||
|
let atlas_handle = texture_atlases.add(texture_atlas);
|
||||||
|
|
||||||
|
// set up a scene to display our texture atlas
|
||||||
|
commands.spawn_bundle(OrthographicCameraBundle::new_2d());
|
||||||
|
// draw a sprite from the atlas
|
||||||
|
commands.spawn_bundle(PipelinedSpriteSheetBundle {
|
||||||
|
transform: Transform {
|
||||||
|
translation: Vec3::new(150.0, 0.0, 0.0),
|
||||||
|
scale: Vec3::splat(4.0),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
sprite: TextureAtlasSprite::new(vendor_index as u32),
|
||||||
|
texture_atlas: atlas_handle,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
// draw the atlas itself
|
||||||
|
commands.spawn_bundle(PipelinedSpriteBundle {
|
||||||
|
sprite: Sprite {
|
||||||
|
size: Vec2::new(512.0, 512.0),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
texture: texture_atlas_texture,
|
||||||
|
transform: Transform::from_xyz(-300.0, 0.0, 0.0),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
}
|
@ -85,6 +85,7 @@ Example | File | Description
|
|||||||
`contributors` | [`2d/contributors.rs`](./2d/contributors.rs) | Displays each contributor as a bouncy bevy-ball!
|
`contributors` | [`2d/contributors.rs`](./2d/contributors.rs) | Displays each contributor as a bouncy bevy-ball!
|
||||||
`many_sprites` | [`2d/many_sprites.rs`](./2d/many_sprites.rs) | Displays many sprites in a grid arragement! Used for performance testing.
|
`many_sprites` | [`2d/many_sprites.rs`](./2d/many_sprites.rs) | Displays many sprites in a grid arragement! Used for performance testing.
|
||||||
`mesh` | [`2d/mesh.rs`](./2d/mesh.rs) | Renders a custom mesh
|
`mesh` | [`2d/mesh.rs`](./2d/mesh.rs) | Renders a custom mesh
|
||||||
|
`pipelined_texture_atlas` | [`2d/pipelined_texture_atlas.rs`](./2d/pipelined_texture_atlas.rs) | Generates a texture atlas (sprite sheet) from individual sprites
|
||||||
`sprite` | [`2d/sprite.rs`](./2d/sprite.rs) | Renders a sprite
|
`sprite` | [`2d/sprite.rs`](./2d/sprite.rs) | Renders a sprite
|
||||||
`sprite_sheet` | [`2d/sprite_sheet.rs`](./2d/sprite_sheet.rs) | Renders an animated sprite
|
`sprite_sheet` | [`2d/sprite_sheet.rs`](./2d/sprite_sheet.rs) | Renders an animated sprite
|
||||||
`text2d` | [`2d/text2d.rs`](./2d/text2d.rs) | Generates text in 2d
|
`text2d` | [`2d/text2d.rs`](./2d/text2d.rs) | Generates text in 2d
|
||||||
|
@ -21,12 +21,16 @@ bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.5.0" }
|
|||||||
bevy_ecs = { path = "../../crates/bevy_ecs", version = "0.5.0" }
|
bevy_ecs = { path = "../../crates/bevy_ecs", version = "0.5.0" }
|
||||||
bevy_log = { path = "../../crates/bevy_log", version = "0.5.0" }
|
bevy_log = { path = "../../crates/bevy_log", version = "0.5.0" }
|
||||||
bevy_math = { path = "../../crates/bevy_math", version = "0.5.0" }
|
bevy_math = { path = "../../crates/bevy_math", version = "0.5.0" }
|
||||||
bevy_reflect = { path = "../../crates/bevy_reflect", version = "0.5.0", features = ["bevy"] }
|
bevy_reflect = { path = "../../crates/bevy_reflect", version = "0.5.0", features = [
|
||||||
|
"bevy",
|
||||||
|
] }
|
||||||
bevy_render2 = { path = "../bevy_render2", version = "0.5.0" }
|
bevy_render2 = { path = "../bevy_render2", version = "0.5.0" }
|
||||||
bevy_transform = { path = "../../crates/bevy_transform", version = "0.5.0" }
|
bevy_transform = { path = "../../crates/bevy_transform", version = "0.5.0" }
|
||||||
bevy_utils = { path = "../../crates/bevy_utils", version = "0.5.0" }
|
bevy_utils = { path = "../../crates/bevy_utils", version = "0.5.0" }
|
||||||
|
|
||||||
# other
|
# other
|
||||||
thiserror = "1.0"
|
|
||||||
serde = { version = "1", features = ["derive"] }
|
|
||||||
bytemuck = "1.5"
|
bytemuck = "1.5"
|
||||||
|
guillotiere = "0.6.0"
|
||||||
|
thiserror = "1.0"
|
||||||
|
rectangle-pack = "0.4"
|
||||||
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
use crate::Sprite;
|
use crate::{
|
||||||
|
texture_atlas::{TextureAtlas, TextureAtlasSprite},
|
||||||
|
Sprite,
|
||||||
|
};
|
||||||
use bevy_asset::Handle;
|
use bevy_asset::Handle;
|
||||||
use bevy_ecs::bundle::Bundle;
|
use bevy_ecs::bundle::Bundle;
|
||||||
use bevy_render2::texture::Image;
|
use bevy_render2::texture::Image;
|
||||||
@ -22,3 +25,27 @@ impl Default for PipelinedSpriteBundle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A Bundle of components for drawing a single sprite from a sprite sheet (also referred
|
||||||
|
/// to as a `TextureAtlas`)
|
||||||
|
#[derive(Bundle, Clone)]
|
||||||
|
pub struct PipelinedSpriteSheetBundle {
|
||||||
|
/// The specific sprite from the texture atlas to be drawn
|
||||||
|
pub sprite: TextureAtlasSprite,
|
||||||
|
/// A handle to the texture atlas that holds the sprite images
|
||||||
|
pub texture_atlas: Handle<TextureAtlas>,
|
||||||
|
/// Data pertaining to how the sprite is drawn on the screen
|
||||||
|
pub transform: Transform,
|
||||||
|
pub global_transform: GlobalTransform,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for PipelinedSpriteSheetBundle {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
sprite: Default::default(),
|
||||||
|
texture_atlas: Default::default(),
|
||||||
|
transform: Default::default(),
|
||||||
|
global_transform: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
101
pipelined/bevy_sprite2/src/dynamic_texture_atlas_builder.rs
Normal file
101
pipelined/bevy_sprite2/src/dynamic_texture_atlas_builder.rs
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
use crate::{Rect, TextureAtlas};
|
||||||
|
use bevy_asset::Assets;
|
||||||
|
use bevy_math::Vec2;
|
||||||
|
use bevy_render2::texture::{Image, TextureFormatPixelInfo};
|
||||||
|
use guillotiere::{size2, Allocation, AtlasAllocator};
|
||||||
|
|
||||||
|
pub struct DynamicTextureAtlasBuilder {
|
||||||
|
pub atlas_allocator: AtlasAllocator,
|
||||||
|
pub padding: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DynamicTextureAtlasBuilder {
|
||||||
|
pub fn new(size: Vec2, padding: i32) -> Self {
|
||||||
|
Self {
|
||||||
|
atlas_allocator: AtlasAllocator::new(to_size2(size)),
|
||||||
|
padding,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_texture(
|
||||||
|
&mut self,
|
||||||
|
texture_atlas: &mut TextureAtlas,
|
||||||
|
textures: &mut Assets<Image>,
|
||||||
|
texture: &Image,
|
||||||
|
) -> Option<u32> {
|
||||||
|
let allocation = self.atlas_allocator.allocate(size2(
|
||||||
|
texture.texture_descriptor.size.width as i32 + self.padding,
|
||||||
|
texture.texture_descriptor.size.height as i32 + self.padding,
|
||||||
|
));
|
||||||
|
if let Some(allocation) = allocation {
|
||||||
|
let atlas_texture = textures.get_mut(&texture_atlas.texture).unwrap();
|
||||||
|
self.place_texture(atlas_texture, allocation, texture);
|
||||||
|
let mut rect: Rect = allocation.rectangle.into();
|
||||||
|
rect.max.x -= self.padding as f32;
|
||||||
|
rect.max.y -= self.padding as f32;
|
||||||
|
texture_atlas.add_texture(rect);
|
||||||
|
Some((texture_atlas.len() - 1) as u32)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fn resize(
|
||||||
|
// &mut self,
|
||||||
|
// texture_atlas: &mut TextureAtlas,
|
||||||
|
// textures: &mut Assets<Texture>,
|
||||||
|
// size: Vec2,
|
||||||
|
// ) {
|
||||||
|
// 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)
|
||||||
|
// { 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,
|
||||||
|
atlas_texture: &mut Image,
|
||||||
|
allocation: Allocation,
|
||||||
|
texture: &Image,
|
||||||
|
) {
|
||||||
|
let mut rect = allocation.rectangle;
|
||||||
|
rect.max.x -= self.padding;
|
||||||
|
rect.max.y -= self.padding;
|
||||||
|
let atlas_width = atlas_texture.texture_descriptor.size.width as usize;
|
||||||
|
let rect_width = rect.width() as usize;
|
||||||
|
let format_size = atlas_texture.texture_descriptor.format.pixel_size();
|
||||||
|
|
||||||
|
for (texture_y, bound_y) in (rect.min.y..rect.max.y).map(|i| i as usize).enumerate() {
|
||||||
|
let begin = (bound_y * atlas_width + rect.min.x as usize) * format_size;
|
||||||
|
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;
|
||||||
|
atlas_texture.data[begin..end]
|
||||||
|
.copy_from_slice(&texture.data[texture_begin..texture_end]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<guillotiere::Rectangle> for Rect {
|
||||||
|
fn from(rectangle: guillotiere::Rectangle) -> Self {
|
||||||
|
Rect {
|
||||||
|
min: Vec2::new(rectangle.min.x as f32, rectangle.min.y as f32),
|
||||||
|
max: Vec2::new(rectangle.max.x as f32, rectangle.max.y as f32),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_size2(vec2: Vec2) -> guillotiere::Size {
|
||||||
|
guillotiere::Size::new(vec2.x as i32, vec2.y as i32)
|
||||||
|
}
|
@ -1,12 +1,19 @@
|
|||||||
mod bundle;
|
mod bundle;
|
||||||
|
mod dynamic_texture_atlas_builder;
|
||||||
mod rect;
|
mod rect;
|
||||||
mod render;
|
mod render;
|
||||||
mod sprite;
|
mod sprite;
|
||||||
|
mod texture_atlas;
|
||||||
|
mod texture_atlas_builder;
|
||||||
|
|
||||||
|
use bevy_asset::AddAsset;
|
||||||
pub use bundle::*;
|
pub use bundle::*;
|
||||||
|
pub use dynamic_texture_atlas_builder::*;
|
||||||
pub use rect::*;
|
pub use rect::*;
|
||||||
pub use render::*;
|
pub use render::*;
|
||||||
pub use sprite::*;
|
pub use sprite::*;
|
||||||
|
pub use texture_atlas::*;
|
||||||
|
pub use texture_atlas_builder::*;
|
||||||
|
|
||||||
use bevy_app::prelude::*;
|
use bevy_app::prelude::*;
|
||||||
use bevy_render2::{render_graph::RenderGraph, render_phase::DrawFunctions, RenderStage};
|
use bevy_render2::{render_graph::RenderGraph, render_phase::DrawFunctions, RenderStage};
|
||||||
@ -16,9 +23,11 @@ pub struct SpritePlugin;
|
|||||||
|
|
||||||
impl Plugin for SpritePlugin {
|
impl Plugin for SpritePlugin {
|
||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
app.register_type::<Sprite>();
|
app.add_asset::<TextureAtlas>().register_type::<Sprite>();
|
||||||
let render_app = app.sub_app_mut(0);
|
let render_app = app.sub_app_mut(0);
|
||||||
render_app
|
render_app
|
||||||
|
.init_resource::<ExtractedSprites>()
|
||||||
|
.add_system_to_stage(RenderStage::Extract, render::extract_atlases)
|
||||||
.add_system_to_stage(RenderStage::Extract, render::extract_sprites)
|
.add_system_to_stage(RenderStage::Extract, render::extract_sprites)
|
||||||
.add_system_to_stage(RenderStage::Prepare, render::prepare_sprites)
|
.add_system_to_stage(RenderStage::Prepare, render::prepare_sprites)
|
||||||
.add_system_to_stage(RenderStage::Queue, queue_sprites)
|
.add_system_to_stage(RenderStage::Queue, queue_sprites)
|
||||||
|
@ -19,4 +19,8 @@ impl Rect {
|
|||||||
pub fn height(&self) -> f32 {
|
pub fn height(&self) -> f32 {
|
||||||
self.max.y - self.min.y
|
self.max.y - self.min.y
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn size(&self) -> Vec2 {
|
||||||
|
Vec2::new(self.width(), self.height())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
use crate::Sprite;
|
use crate::{
|
||||||
|
texture_atlas::{TextureAtlas, TextureAtlasSprite},
|
||||||
|
Rect, Sprite,
|
||||||
|
};
|
||||||
use bevy_asset::{Assets, Handle};
|
use bevy_asset::{Assets, Handle};
|
||||||
use bevy_core_pipeline::Transparent2dPhase;
|
use bevy_core_pipeline::Transparent2dPhase;
|
||||||
use bevy_ecs::{prelude::*, system::SystemState};
|
use bevy_ecs::{prelude::*, system::SystemState};
|
||||||
@ -13,6 +16,7 @@ use bevy_render2::{
|
|||||||
shader::Shader,
|
shader::Shader,
|
||||||
texture::{BevyDefault, Image},
|
texture::{BevyDefault, Image},
|
||||||
view::{ViewMeta, ViewUniform, ViewUniformOffset},
|
view::{ViewMeta, ViewUniform, ViewUniformOffset},
|
||||||
|
RenderWorld,
|
||||||
};
|
};
|
||||||
use bevy_transform::components::GlobalTransform;
|
use bevy_transform::components::GlobalTransform;
|
||||||
use bevy_utils::slab::{FrameSlabMap, FrameSlabMapKey};
|
use bevy_utils::slab::{FrameSlabMap, FrameSlabMapKey};
|
||||||
@ -142,35 +146,68 @@ impl FromWorld for SpriteShaders {
|
|||||||
|
|
||||||
struct ExtractedSprite {
|
struct ExtractedSprite {
|
||||||
transform: Mat4,
|
transform: Mat4,
|
||||||
size: Vec2,
|
rect: Rect,
|
||||||
handle: Handle<Image>,
|
handle: Handle<Image>,
|
||||||
|
atlas_size: Option<Vec2>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
pub struct ExtractedSprites {
|
pub struct ExtractedSprites {
|
||||||
sprites: Vec<ExtractedSprite>,
|
sprites: Vec<ExtractedSprite>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn extract_sprites(
|
pub fn extract_atlases(
|
||||||
mut commands: Commands,
|
texture_atlases: Res<Assets<TextureAtlas>>,
|
||||||
images: Res<Assets<Image>>,
|
atlas_query: Query<(&TextureAtlasSprite, &GlobalTransform, &Handle<TextureAtlas>)>,
|
||||||
query: Query<(&Sprite, &GlobalTransform, &Handle<Image>)>,
|
mut render_world: ResMut<RenderWorld>,
|
||||||
) {
|
) {
|
||||||
let mut extracted_sprites = Vec::new();
|
let mut extracted_sprites = Vec::new();
|
||||||
for (sprite, transform, handle) in query.iter() {
|
for (atlas_sprite, transform, texture_atlas_handle) in atlas_query.iter() {
|
||||||
|
if !texture_atlases.contains(texture_atlas_handle) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(texture_atlas) = texture_atlases.get(texture_atlas_handle) {
|
||||||
|
let rect = texture_atlas.textures[atlas_sprite.index as usize];
|
||||||
|
extracted_sprites.push(ExtractedSprite {
|
||||||
|
atlas_size: Some(texture_atlas.size),
|
||||||
|
transform: transform.compute_matrix(),
|
||||||
|
rect,
|
||||||
|
handle: texture_atlas.texture.clone_weak(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(mut extracted_sprites_res) = render_world.get_resource_mut::<ExtractedSprites>() {
|
||||||
|
extracted_sprites_res.sprites.extend(extracted_sprites);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn extract_sprites(
|
||||||
|
images: Res<Assets<Image>>,
|
||||||
|
sprite_query: Query<(&Sprite, &GlobalTransform, &Handle<Image>)>,
|
||||||
|
mut render_world: ResMut<RenderWorld>,
|
||||||
|
) {
|
||||||
|
let mut extracted_sprites = Vec::new();
|
||||||
|
for (sprite, transform, handle) in sprite_query.iter() {
|
||||||
if !images.contains(handle) {
|
if !images.contains(handle) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
extracted_sprites.push(ExtractedSprite {
|
extracted_sprites.push(ExtractedSprite {
|
||||||
|
atlas_size: None,
|
||||||
transform: transform.compute_matrix(),
|
transform: transform.compute_matrix(),
|
||||||
size: sprite.size,
|
rect: Rect {
|
||||||
|
min: Vec2::ZERO,
|
||||||
|
max: sprite.size,
|
||||||
|
},
|
||||||
handle: handle.clone_weak(),
|
handle: handle.clone_weak(),
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
commands.insert_resource(ExtractedSprites {
|
if let Some(mut extracted_sprites_res) = render_world.get_resource_mut::<ExtractedSprites>() {
|
||||||
sprites: extracted_sprites,
|
extracted_sprites_res.sprites.extend(extracted_sprites);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
@ -228,17 +265,6 @@ pub fn prepare_sprites(
|
|||||||
panic!("expected vec3");
|
panic!("expected vec3");
|
||||||
};
|
};
|
||||||
|
|
||||||
let quad_vertex_uvs = if let VertexAttributeValues::Float32x2(vertex_uvs) = sprite_meta
|
|
||||||
.quad
|
|
||||||
.attribute(Mesh::ATTRIBUTE_UV_0)
|
|
||||||
.unwrap()
|
|
||||||
.clone()
|
|
||||||
{
|
|
||||||
vertex_uvs
|
|
||||||
} else {
|
|
||||||
panic!("expected vec2");
|
|
||||||
};
|
|
||||||
|
|
||||||
let quad_indices = if let Indices::U32(indices) = sprite_meta.quad.indices().unwrap() {
|
let quad_indices = if let Indices::U32(indices) = sprite_meta.quad.indices().unwrap() {
|
||||||
indices.clone()
|
indices.clone()
|
||||||
} else {
|
} else {
|
||||||
@ -255,14 +281,25 @@ pub fn prepare_sprites(
|
|||||||
);
|
);
|
||||||
|
|
||||||
for (i, extracted_sprite) in extracted_sprites.sprites.iter().enumerate() {
|
for (i, extracted_sprite) in extracted_sprites.sprites.iter().enumerate() {
|
||||||
for (vertex_position, vertex_uv) in quad_vertex_positions.iter().zip(quad_vertex_uvs.iter())
|
let sprite_rect = extracted_sprite.rect;
|
||||||
{
|
|
||||||
|
// Specify the corners of the sprite
|
||||||
|
let bottom_left = Vec2::new(sprite_rect.min.x, sprite_rect.max.y);
|
||||||
|
let top_left = sprite_rect.min;
|
||||||
|
let top_right = Vec2::new(sprite_rect.max.x, sprite_rect.min.y);
|
||||||
|
let bottom_right = sprite_rect.max;
|
||||||
|
|
||||||
|
let atlas_positions: [Vec2; 4] = [bottom_left, top_left, top_right, bottom_right];
|
||||||
|
|
||||||
|
for (index, vertex_position) in quad_vertex_positions.iter().enumerate() {
|
||||||
let mut final_position =
|
let mut final_position =
|
||||||
Vec3::from(*vertex_position) * extracted_sprite.size.extend(1.0);
|
Vec3::from(*vertex_position) * extracted_sprite.rect.size().extend(1.0);
|
||||||
final_position = (extracted_sprite.transform * final_position.extend(1.0)).xyz();
|
final_position = (extracted_sprite.transform * final_position.extend(1.0)).xyz();
|
||||||
sprite_meta.vertices.push(SpriteVertex {
|
sprite_meta.vertices.push(SpriteVertex {
|
||||||
position: final_position.into(),
|
position: final_position.into(),
|
||||||
uv: *vertex_uv,
|
uv: (atlas_positions[index]
|
||||||
|
/ extracted_sprite.atlas_size.unwrap_or(sprite_rect.max))
|
||||||
|
.into(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -284,7 +321,7 @@ pub fn queue_sprites(
|
|||||||
mut sprite_meta: ResMut<SpriteMeta>,
|
mut sprite_meta: ResMut<SpriteMeta>,
|
||||||
view_meta: Res<ViewMeta>,
|
view_meta: Res<ViewMeta>,
|
||||||
sprite_shaders: Res<SpriteShaders>,
|
sprite_shaders: Res<SpriteShaders>,
|
||||||
extracted_sprites: Res<ExtractedSprites>,
|
mut extracted_sprites: ResMut<ExtractedSprites>,
|
||||||
gpu_images: Res<RenderAssets<Image>>,
|
gpu_images: Res<RenderAssets<Image>>,
|
||||||
mut views: Query<&mut RenderPhase<Transparent2dPhase>>,
|
mut views: Query<&mut RenderPhase<Transparent2dPhase>>,
|
||||||
) {
|
) {
|
||||||
@ -340,6 +377,8 @@ pub fn queue_sprites(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extracted_sprites.sprites.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: this logic can be moved to prepare_sprites once wgpu::Queue is exposed directly
|
// TODO: this logic can be moved to prepare_sprites once wgpu::Queue is exposed directly
|
||||||
|
145
pipelined/bevy_sprite2/src/texture_atlas.rs
Normal file
145
pipelined/bevy_sprite2/src/texture_atlas.rs
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
use crate::Rect;
|
||||||
|
use bevy_asset::Handle;
|
||||||
|
use bevy_math::Vec2;
|
||||||
|
use bevy_reflect::{Reflect, TypeUuid};
|
||||||
|
use bevy_render2::{color::Color, texture::Image};
|
||||||
|
use bevy_utils::HashMap;
|
||||||
|
|
||||||
|
/// An atlas containing multiple textures (like a spritesheet or a tilemap).
|
||||||
|
/// [Example usage animating sprite.](https://github.com/bevyengine/bevy/blob/latest/examples/2d/sprite_sheet.rs)
|
||||||
|
/// [Example usage loading sprite sheet.](https://github.com/bevyengine/bevy/blob/latest/examples/2d/texture_atlas.rs)
|
||||||
|
#[derive(Debug, Clone, TypeUuid)]
|
||||||
|
#[uuid = "7233c597-ccfa-411f-bd59-9af349432ada"]
|
||||||
|
pub struct TextureAtlas {
|
||||||
|
/// The handle to the texture in which the sprites are stored
|
||||||
|
pub texture: Handle<Image>,
|
||||||
|
// TODO: add support to Uniforms derive to write dimensions and sprites to the same buffer
|
||||||
|
pub size: Vec2,
|
||||||
|
/// The specific areas of the atlas where each texture can be found
|
||||||
|
pub textures: Vec<Rect>,
|
||||||
|
pub texture_handles: Option<HashMap<Handle<Image>, usize>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, TypeUuid, Reflect)]
|
||||||
|
#[uuid = "7233c597-ccfa-411f-bd59-9af349432ada"]
|
||||||
|
pub struct TextureAtlasSprite {
|
||||||
|
pub color: Color,
|
||||||
|
pub index: u32,
|
||||||
|
pub flip_x: bool,
|
||||||
|
pub flip_y: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for TextureAtlasSprite {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
index: 0,
|
||||||
|
color: Color::WHITE,
|
||||||
|
flip_x: false,
|
||||||
|
flip_y: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TextureAtlasSprite {
|
||||||
|
pub fn new(index: u32) -> TextureAtlasSprite {
|
||||||
|
Self {
|
||||||
|
index,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TextureAtlas {
|
||||||
|
/// Create a new `TextureAtlas` that has a texture, but does not have
|
||||||
|
/// any individual sprites specified
|
||||||
|
pub fn new_empty(texture: Handle<Image>, dimensions: Vec2) -> Self {
|
||||||
|
Self {
|
||||||
|
texture,
|
||||||
|
size: dimensions,
|
||||||
|
texture_handles: None,
|
||||||
|
textures: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate a `TextureAtlas` by splitting a texture into a grid where each
|
||||||
|
/// cell of the grid of `tile_size` is one of the textures in the atlas
|
||||||
|
pub fn from_grid(
|
||||||
|
texture: Handle<Image>,
|
||||||
|
tile_size: Vec2,
|
||||||
|
columns: usize,
|
||||||
|
rows: usize,
|
||||||
|
) -> TextureAtlas {
|
||||||
|
Self::from_grid_with_padding(texture, tile_size, columns, rows, Vec2::new(0f32, 0f32))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate a `TextureAtlas` by splitting a texture into a grid where each
|
||||||
|
/// cell of the grid of `tile_size` is one of the textures in the atlas and is separated by
|
||||||
|
/// some `padding` in the texture
|
||||||
|
pub fn from_grid_with_padding(
|
||||||
|
texture: Handle<Image>,
|
||||||
|
tile_size: Vec2,
|
||||||
|
columns: usize,
|
||||||
|
rows: usize,
|
||||||
|
padding: Vec2,
|
||||||
|
) -> TextureAtlas {
|
||||||
|
let mut sprites = Vec::new();
|
||||||
|
let mut x_padding = 0.0;
|
||||||
|
let mut y_padding = 0.0;
|
||||||
|
|
||||||
|
for y in 0..rows {
|
||||||
|
if y > 0 {
|
||||||
|
y_padding = padding.y;
|
||||||
|
}
|
||||||
|
for x in 0..columns {
|
||||||
|
if x > 0 {
|
||||||
|
x_padding = padding.x;
|
||||||
|
}
|
||||||
|
|
||||||
|
let rect_min = Vec2::new(
|
||||||
|
(tile_size.x + x_padding) * x as f32,
|
||||||
|
(tile_size.y + y_padding) * y as f32,
|
||||||
|
);
|
||||||
|
|
||||||
|
sprites.push(Rect {
|
||||||
|
min: rect_min,
|
||||||
|
max: Vec2::new(rect_min.x + tile_size.x, rect_min.y + tile_size.y),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TextureAtlas {
|
||||||
|
size: Vec2::new(
|
||||||
|
((tile_size.x + x_padding) * columns as f32) - x_padding,
|
||||||
|
((tile_size.y + y_padding) * rows as f32) - y_padding,
|
||||||
|
),
|
||||||
|
textures: sprites,
|
||||||
|
texture,
|
||||||
|
texture_handles: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a sprite to the list of textures in the `TextureAtlas`
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `rect` - The section of the atlas that contains the texture to be added,
|
||||||
|
/// from the top-left corner of the texture to the bottom-right corner
|
||||||
|
pub fn add_texture(&mut self, rect: Rect) {
|
||||||
|
self.textures.push(rect);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// How many textures are in the `TextureAtlas`
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.textures.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.textures.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_texture_index(&self, texture: &Handle<Image>) -> Option<usize> {
|
||||||
|
self.texture_handles
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|texture_handles| texture_handles.get(texture).cloned())
|
||||||
|
}
|
||||||
|
}
|
236
pipelined/bevy_sprite2/src/texture_atlas_builder.rs
Normal file
236
pipelined/bevy_sprite2/src/texture_atlas_builder.rs
Normal file
@ -0,0 +1,236 @@
|
|||||||
|
use bevy_asset::{Assets, Handle};
|
||||||
|
use bevy_log::{debug, error, warn};
|
||||||
|
use bevy_math::Vec2;
|
||||||
|
use bevy_render2::{
|
||||||
|
render_resource::{Extent3d, TextureDimension, TextureFormat},
|
||||||
|
texture::{Image, TextureFormatPixelInfo},
|
||||||
|
};
|
||||||
|
use bevy_utils::HashMap;
|
||||||
|
use rectangle_pack::{
|
||||||
|
contains_smallest_box, pack_rects, volume_heuristic, GroupedRectsToPlace, PackedLocation,
|
||||||
|
RectToInsert, TargetBin,
|
||||||
|
};
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
use crate::{texture_atlas::TextureAtlas, Rect};
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum TextureAtlasBuilderError {
|
||||||
|
#[error("could not pack textures into an atlas within the given bounds")]
|
||||||
|
NotEnoughSpace,
|
||||||
|
#[error("added a texture with the wrong format in an atlas")]
|
||||||
|
WrongFormat,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
/// A builder which is used to create a texture atlas from many individual
|
||||||
|
/// sprites.
|
||||||
|
pub struct TextureAtlasBuilder {
|
||||||
|
/// The grouped rects which must be placed with a key value pair of a
|
||||||
|
/// texture handle to an index.
|
||||||
|
rects_to_place: GroupedRectsToPlace<Handle<Image>>,
|
||||||
|
/// The initial atlas size in pixels.
|
||||||
|
initial_size: Vec2,
|
||||||
|
/// The absolute maximum size of the texture atlas in pixels.
|
||||||
|
max_size: Vec2,
|
||||||
|
/// The texture format for the textures that will be loaded in the atlas.
|
||||||
|
format: TextureFormat,
|
||||||
|
/// Enable automatic format conversion for textures if they are not in the atlas format.
|
||||||
|
auto_format_conversion: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for TextureAtlasBuilder {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
rects_to_place: GroupedRectsToPlace::new(),
|
||||||
|
initial_size: Vec2::new(256., 256.),
|
||||||
|
max_size: Vec2::new(2048., 2048.),
|
||||||
|
format: TextureFormat::Rgba8UnormSrgb,
|
||||||
|
auto_format_conversion: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type TextureAtlasBuilderResult<T> = Result<T, TextureAtlasBuilderError>;
|
||||||
|
|
||||||
|
impl TextureAtlasBuilder {
|
||||||
|
/// Sets the initial size of the atlas in pixels.
|
||||||
|
pub fn initial_size(mut self, size: Vec2) -> Self {
|
||||||
|
self.initial_size = size;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the max size of the atlas in pixels.
|
||||||
|
pub fn max_size(mut self, size: Vec2) -> Self {
|
||||||
|
self.max_size = size;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the texture format for textures in the atlas.
|
||||||
|
pub fn format(mut self, format: TextureFormat) -> Self {
|
||||||
|
self.format = format;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Control whether the added texture should be converted to the atlas format, if different.
|
||||||
|
pub fn auto_format_conversion(mut self, auto_format_conversion: bool) -> Self {
|
||||||
|
self.auto_format_conversion = auto_format_conversion;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds a texture to be copied to the texture atlas.
|
||||||
|
pub fn add_texture(&mut self, texture_handle: Handle<Image>, texture: &Image) {
|
||||||
|
self.rects_to_place.push_rect(
|
||||||
|
texture_handle,
|
||||||
|
None,
|
||||||
|
RectToInsert::new(
|
||||||
|
texture.texture_descriptor.size.width,
|
||||||
|
texture.texture_descriptor.size.height,
|
||||||
|
1,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn copy_texture_to_atlas(
|
||||||
|
atlas_texture: &mut Image,
|
||||||
|
texture: &Image,
|
||||||
|
packed_location: &PackedLocation,
|
||||||
|
) {
|
||||||
|
let rect_width = packed_location.width() as usize;
|
||||||
|
let rect_height = packed_location.height() as usize;
|
||||||
|
let rect_x = packed_location.x() as usize;
|
||||||
|
let rect_y = packed_location.y() as usize;
|
||||||
|
let atlas_width = atlas_texture.texture_descriptor.size.width as usize;
|
||||||
|
let format_size = atlas_texture.texture_descriptor.format.pixel_size();
|
||||||
|
|
||||||
|
for (texture_y, bound_y) in (rect_y..rect_y + rect_height).enumerate() {
|
||||||
|
let begin = (bound_y * atlas_width + rect_x) * format_size;
|
||||||
|
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;
|
||||||
|
atlas_texture.data[begin..end]
|
||||||
|
.copy_from_slice(&texture.data[texture_begin..texture_end]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn copy_converted_texture(
|
||||||
|
&self,
|
||||||
|
atlas_texture: &mut Image,
|
||||||
|
texture: &Image,
|
||||||
|
packed_location: &PackedLocation,
|
||||||
|
) {
|
||||||
|
if self.format == texture.texture_descriptor.format {
|
||||||
|
Self::copy_texture_to_atlas(atlas_texture, texture, packed_location);
|
||||||
|
} else if let Some(converted_texture) = texture.convert(self.format) {
|
||||||
|
debug!(
|
||||||
|
"Converting texture from '{:?}' to '{:?}'",
|
||||||
|
texture.texture_descriptor.format, self.format
|
||||||
|
);
|
||||||
|
Self::copy_texture_to_atlas(atlas_texture, &converted_texture, packed_location);
|
||||||
|
} else {
|
||||||
|
error!(
|
||||||
|
"Error converting texture from '{:?}' to '{:?}', ignoring",
|
||||||
|
texture.texture_descriptor.format, self.format
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Consumes the builder and returns a result with a new texture atlas.
|
||||||
|
///
|
||||||
|
/// Internally it copies all rectangles from the textures and copies them
|
||||||
|
/// into a new texture which the texture atlas will use. It is not useful to
|
||||||
|
/// hold a strong handle to the texture afterwards else it will exist twice
|
||||||
|
/// in memory.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// If there is not enough space in the atlas texture, an error will
|
||||||
|
/// be returned. It is then recommended to make a larger sprite sheet.
|
||||||
|
pub fn finish(
|
||||||
|
self,
|
||||||
|
textures: &mut Assets<Image>,
|
||||||
|
) -> Result<TextureAtlas, TextureAtlasBuilderError> {
|
||||||
|
let initial_width = self.initial_size.x as u32;
|
||||||
|
let initial_height = self.initial_size.y as u32;
|
||||||
|
let max_width = self.max_size.x as u32;
|
||||||
|
let max_height = self.max_size.y as u32;
|
||||||
|
|
||||||
|
let mut current_width = initial_width;
|
||||||
|
let mut current_height = initial_height;
|
||||||
|
let mut rect_placements = None;
|
||||||
|
let mut atlas_texture = Image::default();
|
||||||
|
|
||||||
|
while rect_placements.is_none() {
|
||||||
|
if current_width > max_width || current_height > max_height {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
let last_attempt = current_height == max_height && current_width == max_width;
|
||||||
|
|
||||||
|
let mut target_bins = std::collections::BTreeMap::new();
|
||||||
|
target_bins.insert(0, TargetBin::new(current_width, current_height, 1));
|
||||||
|
rect_placements = match pack_rects(
|
||||||
|
&self.rects_to_place,
|
||||||
|
&mut target_bins,
|
||||||
|
&volume_heuristic,
|
||||||
|
&contains_smallest_box,
|
||||||
|
) {
|
||||||
|
Ok(rect_placements) => {
|
||||||
|
atlas_texture = Image::new_fill(
|
||||||
|
Extent3d {
|
||||||
|
width: current_width,
|
||||||
|
height: current_height,
|
||||||
|
depth_or_array_layers: 1,
|
||||||
|
},
|
||||||
|
TextureDimension::D2,
|
||||||
|
&[0, 0, 0, 0],
|
||||||
|
self.format,
|
||||||
|
);
|
||||||
|
Some(rect_placements)
|
||||||
|
}
|
||||||
|
Err(rectangle_pack::RectanglePackError::NotEnoughBinSpace) => {
|
||||||
|
current_height = (current_height * 2).clamp(0, max_height);
|
||||||
|
current_width = (current_width * 2).clamp(0, max_width);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if last_attempt {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let rect_placements = rect_placements.ok_or(TextureAtlasBuilderError::NotEnoughSpace)?;
|
||||||
|
|
||||||
|
let mut texture_rects = Vec::with_capacity(rect_placements.packed_locations().len());
|
||||||
|
let mut texture_handles = HashMap::default();
|
||||||
|
for (texture_handle, (_, packed_location)) in rect_placements.packed_locations().iter() {
|
||||||
|
let texture = textures.get(texture_handle).unwrap();
|
||||||
|
let min = Vec2::new(packed_location.x() as f32, packed_location.y() as f32);
|
||||||
|
let max = min
|
||||||
|
+ Vec2::new(
|
||||||
|
packed_location.width() as f32,
|
||||||
|
packed_location.height() as f32,
|
||||||
|
);
|
||||||
|
texture_handles.insert(texture_handle.clone_weak(), texture_rects.len());
|
||||||
|
texture_rects.push(Rect { min, max });
|
||||||
|
if texture.texture_descriptor.format != self.format && !self.auto_format_conversion {
|
||||||
|
warn!(
|
||||||
|
"Loading a texture of format '{:?}' in an atlas with format '{:?}'",
|
||||||
|
texture.texture_descriptor.format, self.format
|
||||||
|
);
|
||||||
|
return Err(TextureAtlasBuilderError::WrongFormat);
|
||||||
|
}
|
||||||
|
self.copy_converted_texture(&mut atlas_texture, texture, packed_location);
|
||||||
|
}
|
||||||
|
Ok(TextureAtlas {
|
||||||
|
size: Vec2::new(
|
||||||
|
atlas_texture.texture_descriptor.size.width as f32,
|
||||||
|
atlas_texture.texture_descriptor.size.height as f32,
|
||||||
|
),
|
||||||
|
texture: textures.add(atlas_texture),
|
||||||
|
textures: texture_rects,
|
||||||
|
texture_handles: Some(texture_handles),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user