add texture atlases
This commit is contained in:
		
							parent
							
								
									ffc4246a74
								
							
						
					
					
						commit
						2705e5cbb4
					
				@ -64,6 +64,10 @@ path = "examples/2d/sprite.rs"
 | 
				
			|||||||
name = "sprite_sheet"
 | 
					name = "sprite_sheet"
 | 
				
			||||||
path = "examples/2d/sprite_sheet.rs"
 | 
					path = "examples/2d/sprite_sheet.rs"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[example]]
 | 
				
			||||||
 | 
					name = "texture_atlas"
 | 
				
			||||||
 | 
					path = "examples/2d/texture_atlas.rs"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[example]]
 | 
					[[example]]
 | 
				
			||||||
name = "load_model"
 | 
					name = "load_model"
 | 
				
			||||||
path = "examples/3d/load_model.rs"
 | 
					path = "examples/3d/load_model.rs"
 | 
				
			||||||
 | 
				
			|||||||
@ -46,6 +46,7 @@ fn get_primitive_topology(mode: Mode) -> Result<PrimitiveTopology, GltfError> {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TODO: this should return a scene
 | 
				
			||||||
pub fn load_gltf(asset_path: &Path, bytes: Vec<u8>) -> Result<Mesh, GltfError> {
 | 
					pub fn load_gltf(asset_path: &Path, bytes: Vec<u8>) -> Result<Mesh, GltfError> {
 | 
				
			||||||
    let gltf = gltf::Gltf::from_slice(&bytes)?;
 | 
					    let gltf = gltf::Gltf::from_slice(&bytes)?;
 | 
				
			||||||
    let buffer_data = load_buffers(gltf.buffers(), asset_path)?;
 | 
					    let buffer_data = load_buffers(gltf.buffers(), asset_path)?;
 | 
				
			||||||
 | 
				
			|||||||
@ -36,6 +36,7 @@ impl Default for PerspectiveProjection {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TODO: make this a component instead of a property
 | 
				
			||||||
#[derive(Debug, Clone, Property, Serialize, Deserialize)]
 | 
					#[derive(Debug, Clone, Property, Serialize, Deserialize)]
 | 
				
			||||||
pub enum WindowOrigin {
 | 
					pub enum WindowOrigin {
 | 
				
			||||||
    Center,
 | 
					    Center,
 | 
				
			||||||
 | 
				
			|||||||
@ -91,7 +91,10 @@ impl AppPlugin for RenderPlugin {
 | 
				
			|||||||
            .init_resource::<EntityRenderResourceAssignments>()
 | 
					            .init_resource::<EntityRenderResourceAssignments>()
 | 
				
			||||||
            .init_resource::<EntitiesWaitingForAssets>()
 | 
					            .init_resource::<EntitiesWaitingForAssets>()
 | 
				
			||||||
            .init_resource::<TextureResourceSystemState>()
 | 
					            .init_resource::<TextureResourceSystemState>()
 | 
				
			||||||
            .add_system(entity_render_resource_assignments_system())
 | 
					            .add_system_to_stage(
 | 
				
			||||||
 | 
					                bevy_app::stage::POST_UPDATE,
 | 
				
			||||||
 | 
					                entity_render_resource_assignments_system(),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
            .init_system_to_stage(
 | 
					            .init_system_to_stage(
 | 
				
			||||||
                bevy_app::stage::POST_UPDATE,
 | 
					                bevy_app::stage::POST_UPDATE,
 | 
				
			||||||
                camera::camera_system::<OrthographicProjection>,
 | 
					                camera::camera_system::<OrthographicProjection>,
 | 
				
			||||||
 | 
				
			|||||||
@ -14,5 +14,7 @@ bevy_type_registry = { path = "../bevy_type_registry" }
 | 
				
			|||||||
bevy_derive = { path = "../bevy_derive" }
 | 
					bevy_derive = { path = "../bevy_derive" }
 | 
				
			||||||
bevy_render = { path = "../bevy_render" }
 | 
					bevy_render = { path = "../bevy_render" }
 | 
				
			||||||
bevy_transform = { path = "../bevy_transform" }
 | 
					bevy_transform = { path = "../bevy_transform" }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					legion = { path = "../bevy_legion", features = ["serialize"] }
 | 
				
			||||||
glam = "0.8.7"
 | 
					glam = "0.8.7"
 | 
				
			||||||
legion = { path = "../bevy_legion", features = ["serialize"] }
 | 
					guillotiere = "0.5.2"
 | 
				
			||||||
@ -1,6 +1,6 @@
 | 
				
			|||||||
use crate::{
 | 
					use crate::{
 | 
				
			||||||
    render::SPRITE_PIPELINE_HANDLE, sprite::Sprite, ColorMaterial, Quad, SpriteSheet,
 | 
					    render::SPRITE_PIPELINE_HANDLE, sprite::Sprite, ColorMaterial, Quad, TextureAtlas,
 | 
				
			||||||
    SpriteSheetSprite, QUAD_HANDLE, SPRITE_SHEET_PIPELINE_HANDLE,
 | 
					    TextureAtlasSprite, QUAD_HANDLE, SPRITE_SHEET_PIPELINE_HANDLE,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
use bevy_asset::Handle;
 | 
					use bevy_asset::Handle;
 | 
				
			||||||
use bevy_derive::EntityArchetype;
 | 
					use bevy_derive::EntityArchetype;
 | 
				
			||||||
@ -32,8 +32,8 @@ impl Default for SpriteEntity {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#[derive(EntityArchetype)]
 | 
					#[derive(EntityArchetype)]
 | 
				
			||||||
pub struct SpriteSheetEntity {
 | 
					pub struct SpriteSheetEntity {
 | 
				
			||||||
    pub sprite: SpriteSheetSprite,
 | 
					    pub sprite: TextureAtlasSprite,
 | 
				
			||||||
    pub sprite_sheet: Handle<SpriteSheet>,
 | 
					    pub texture_atlas: Handle<TextureAtlas>,
 | 
				
			||||||
    pub renderable: Renderable,
 | 
					    pub renderable: Renderable,
 | 
				
			||||||
    pub mesh: Handle<Mesh>, // TODO: maybe abstract this out
 | 
					    pub mesh: Handle<Mesh>, // TODO: maybe abstract this out
 | 
				
			||||||
                            // pub local_to_world: LocalToWorld,
 | 
					                            // pub local_to_world: LocalToWorld,
 | 
				
			||||||
@ -46,7 +46,7 @@ impl Default for SpriteSheetEntity {
 | 
				
			|||||||
    fn default() -> Self {
 | 
					    fn default() -> Self {
 | 
				
			||||||
        Self {
 | 
					        Self {
 | 
				
			||||||
            sprite: Default::default(),
 | 
					            sprite: Default::default(),
 | 
				
			||||||
            sprite_sheet: Default::default(),
 | 
					            texture_atlas: Default::default(),
 | 
				
			||||||
            renderable: Renderable {
 | 
					            renderable: Renderable {
 | 
				
			||||||
                pipelines: vec![SPRITE_SHEET_PIPELINE_HANDLE],
 | 
					                pipelines: vec![SPRITE_SHEET_PIPELINE_HANDLE],
 | 
				
			||||||
                ..Default::default()
 | 
					                ..Default::default()
 | 
				
			||||||
 | 
				
			|||||||
@ -4,14 +4,16 @@ mod quad;
 | 
				
			|||||||
mod rect;
 | 
					mod rect;
 | 
				
			||||||
mod render;
 | 
					mod render;
 | 
				
			||||||
mod sprite;
 | 
					mod sprite;
 | 
				
			||||||
mod sprite_sheet;
 | 
					mod texture_atlas;
 | 
				
			||||||
 | 
					mod texture_atlas_builder;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub use color_material::*;
 | 
					pub use color_material::*;
 | 
				
			||||||
pub use quad::*;
 | 
					pub use quad::*;
 | 
				
			||||||
pub use rect::*;
 | 
					pub use rect::*;
 | 
				
			||||||
pub use render::*;
 | 
					pub use render::*;
 | 
				
			||||||
pub use sprite::*;
 | 
					pub use sprite::*;
 | 
				
			||||||
pub use sprite_sheet::*;
 | 
					pub use texture_atlas::*;
 | 
				
			||||||
 | 
					pub use texture_atlas_builder::*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use bevy_app::{stage, AppBuilder, AppPlugin};
 | 
					use bevy_app::{stage, AppBuilder, AppPlugin};
 | 
				
			||||||
use bevy_asset::{AddAsset, Assets, Handle};
 | 
					use bevy_asset::{AddAsset, Assets, Handle};
 | 
				
			||||||
@ -32,7 +34,7 @@ pub const QUAD_HANDLE: Handle<Mesh> = Handle::from_u128(142404619811301375266013
 | 
				
			|||||||
impl AppPlugin for SpritePlugin {
 | 
					impl AppPlugin for SpritePlugin {
 | 
				
			||||||
    fn build(&self, app: &mut AppBuilder) {
 | 
					    fn build(&self, app: &mut AppBuilder) {
 | 
				
			||||||
        app.add_asset::<ColorMaterial>()
 | 
					        app.add_asset::<ColorMaterial>()
 | 
				
			||||||
            .add_asset::<SpriteSheet>()
 | 
					            .add_asset::<TextureAtlas>()
 | 
				
			||||||
            .add_system_to_stage(stage::POST_UPDATE, sprite_system())
 | 
					            .add_system_to_stage(stage::POST_UPDATE, sprite_system())
 | 
				
			||||||
            .add_system_to_stage(
 | 
					            .add_system_to_stage(
 | 
				
			||||||
                stage::POST_UPDATE,
 | 
					                stage::POST_UPDATE,
 | 
				
			||||||
 | 
				
			|||||||
@ -1,4 +1,4 @@
 | 
				
			|||||||
use crate::{ColorMaterial, Quad, SpriteSheet, SpriteSheetSprite};
 | 
					use crate::{ColorMaterial, Quad, TextureAtlas, TextureAtlasSprite};
 | 
				
			||||||
use bevy_asset::{Assets, Handle};
 | 
					use bevy_asset::{Assets, Handle};
 | 
				
			||||||
use bevy_render::{
 | 
					use bevy_render::{
 | 
				
			||||||
    base_render_graph,
 | 
					    base_render_graph,
 | 
				
			||||||
@ -135,12 +135,12 @@ impl SpriteRenderGraphBuilder for RenderGraph {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        self.add_system_node(
 | 
					        self.add_system_node(
 | 
				
			||||||
            node::SPRITE_SHEET,
 | 
					            node::SPRITE_SHEET,
 | 
				
			||||||
            AssetUniformNode::<SpriteSheet>::new(false),
 | 
					            AssetUniformNode::<TextureAtlas>::new(false),
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.add_system_node(
 | 
					        self.add_system_node(
 | 
				
			||||||
            node::SPRITE_SHEET_SPRITE,
 | 
					            node::SPRITE_SHEET_SPRITE,
 | 
				
			||||||
            UniformNode::<SpriteSheetSprite>::new(true),
 | 
					            UniformNode::<TextureAtlasSprite>::new(true),
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let mut pipelines = resources.get_mut::<Assets<PipelineDescriptor>>().unwrap();
 | 
					        let mut pipelines = resources.get_mut::<Assets<PipelineDescriptor>>().unwrap();
 | 
				
			||||||
 | 
				
			|||||||
@ -4,11 +4,11 @@ layout(location = 0) in vec2 v_Uv;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
layout(location = 0) out vec4 o_Target;
 | 
					layout(location = 0) out vec4 o_Target;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
layout(set = 1, binding = 2) uniform texture2D SpriteSheet_texture;
 | 
					layout(set = 1, binding = 2) uniform texture2D TextureAtlas_texture;
 | 
				
			||||||
layout(set = 1, binding = 3) uniform sampler SpriteSheet_texture_sampler;
 | 
					layout(set = 1, binding = 3) uniform sampler TextureAtlas_texture_sampler;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void main() {
 | 
					void main() {
 | 
				
			||||||
    o_Target = texture(
 | 
					    o_Target = texture(
 | 
				
			||||||
        sampler2D(SpriteSheet_texture, SpriteSheet_texture_sampler),
 | 
					        sampler2D(TextureAtlas_texture, TextureAtlas_texture_sampler),
 | 
				
			||||||
        v_Uv);
 | 
					        v_Uv);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -4,11 +4,6 @@ layout(location = 0) in vec3 Vertex_Position;
 | 
				
			|||||||
layout(location = 1) in vec3 Vertex_Normal;
 | 
					layout(location = 1) in vec3 Vertex_Normal;
 | 
				
			||||||
layout(location = 2) in vec2 Vertex_Uv;
 | 
					layout(location = 2) in vec2 Vertex_Uv;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// TODO: uncomment when instancing is implemented
 | 
					 | 
				
			||||||
// sprite 
 | 
					 | 
				
			||||||
// layout(location = 0) in vec3 Sprite_Position;
 | 
					 | 
				
			||||||
// layout(location = 1) in int Sprite_Index; 
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
layout(location = 0) out vec2 v_Uv;
 | 
					layout(location = 0) out vec2 v_Uv;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
layout(set = 0, binding = 0) uniform Camera2d {
 | 
					layout(set = 0, binding = 0) uniform Camera2d {
 | 
				
			||||||
@ -16,7 +11,7 @@ layout(set = 0, binding = 0) uniform Camera2d {
 | 
				
			|||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// TODO: merge dimensions into "sprites" buffer when that is supported in the Uniforms derive abstraction
 | 
					// TODO: merge dimensions into "sprites" buffer when that is supported in the Uniforms derive abstraction
 | 
				
			||||||
layout(set = 1, binding = 0) uniform SpriteSheet_dimensions {
 | 
					layout(set = 1, binding = 0) uniform TextureAtlas_dimensions {
 | 
				
			||||||
    vec2 Dimensions;
 | 
					    vec2 Dimensions;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -25,21 +20,21 @@ struct Rect {
 | 
				
			|||||||
    vec2 end;
 | 
					    vec2 end;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
layout(set = 1, binding = 1) buffer SpriteSheet_sprites {
 | 
					layout(set = 1, binding = 1) buffer TextureAtlas_textures {
 | 
				
			||||||
    Rect[] Sprites;
 | 
					    Rect[] Textures;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
layout(set = 2, binding = 0) uniform SpriteSheetSprite {
 | 
					layout(set = 2, binding = 0) uniform TextureAtlasSprite {
 | 
				
			||||||
    vec3 SpriteSheetSprite_position;
 | 
					    vec3 TextureAtlasSprite_position;
 | 
				
			||||||
    float SpriteSheetSprite_scale;
 | 
					    float TextureAtlasSprite_scale;
 | 
				
			||||||
    uint SpriteSheetSprite_index;
 | 
					    uint TextureAtlasSprite_index;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void main() {
 | 
					void main() {
 | 
				
			||||||
    Rect sprite_rect = Sprites[SpriteSheetSprite_index];
 | 
					    Rect sprite_rect = Textures[TextureAtlasSprite_index];
 | 
				
			||||||
    vec2 sprite_dimensions = sprite_rect.end - sprite_rect.begin;
 | 
					    vec2 sprite_dimensions = sprite_rect.end - sprite_rect.begin;
 | 
				
			||||||
    vec3 vertex_position = vec3(Vertex_Position.xy * sprite_dimensions * SpriteSheetSprite_scale, 0.0) + SpriteSheetSprite_position;
 | 
					    vec3 vertex_position = vec3(Vertex_Position.xy * sprite_dimensions * TextureAtlasSprite_scale, 0.0) + TextureAtlasSprite_position;
 | 
				
			||||||
    vec2 uvs[4] = vec2[](
 | 
					    vec2 uvs[4] = vec2[](
 | 
				
			||||||
        vec2(sprite_rect.begin.x, sprite_rect.end.y),
 | 
					        vec2(sprite_rect.begin.x, sprite_rect.end.y),
 | 
				
			||||||
        sprite_rect.begin,
 | 
					        sprite_rect.begin,
 | 
				
			||||||
 | 
				
			|||||||
@ -3,50 +3,60 @@ use bevy_asset::Handle;
 | 
				
			|||||||
use bevy_derive::{Bytes, Uniform, Uniforms};
 | 
					use bevy_derive::{Bytes, Uniform, Uniforms};
 | 
				
			||||||
use bevy_render::texture::Texture;
 | 
					use bevy_render::texture::Texture;
 | 
				
			||||||
use glam::{Vec2, Vec3};
 | 
					use glam::{Vec2, Vec3};
 | 
				
			||||||
 | 
					use std::collections::HashMap;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Uniforms)]
 | 
					#[derive(Uniforms)]
 | 
				
			||||||
pub struct SpriteSheet {
 | 
					pub struct TextureAtlas {
 | 
				
			||||||
    pub texture: Handle<Texture>,
 | 
					    pub texture: Handle<Texture>,
 | 
				
			||||||
    // TODO: add support to Uniforms derive to write dimensions and sprites to the same buffer
 | 
					    // TODO: add support to Uniforms derive to write dimensions and sprites to the same buffer
 | 
				
			||||||
    pub dimensions: Vec2,
 | 
					    pub dimensions: Vec2,
 | 
				
			||||||
    #[uniform(buffer)]
 | 
					    #[uniform(buffer)]
 | 
				
			||||||
    pub sprites: Vec<Rect>,
 | 
					    pub textures: Vec<Rect>,
 | 
				
			||||||
 | 
					    #[uniform(ignore)]
 | 
				
			||||||
 | 
					    pub texture_handles: Option<HashMap<Handle<Texture>, usize>>,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// NOTE: cannot do `unsafe impl Byteable` here because Vec3 takes up the space of a Vec4. If/when glam changes this we can swap out
 | 
					// NOTE: cannot do `unsafe impl Byteable` here because Vec3 takes up the space of a Vec4. If/when glam changes this we can swap out
 | 
				
			||||||
// Bytes for Byteable as a micro-optimization. https://github.com/bitshifter/glam-rs/issues/36
 | 
					// Bytes for Byteable as a micro-optimization. https://github.com/bitshifter/glam-rs/issues/36
 | 
				
			||||||
#[derive(Uniform, Bytes, Default)]
 | 
					#[derive(Uniform, Bytes, Default)]
 | 
				
			||||||
pub struct SpriteSheetSprite {
 | 
					pub struct TextureAtlasSprite {
 | 
				
			||||||
    pub position: Vec3,
 | 
					    pub position: Vec3,
 | 
				
			||||||
    pub scale: f32,
 | 
					    pub scale: f32,
 | 
				
			||||||
    pub index: u32,
 | 
					    pub index: u32,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl SpriteSheet {
 | 
					impl TextureAtlas {
 | 
				
			||||||
    pub fn from_grid(
 | 
					    pub fn from_grid(
 | 
				
			||||||
        texture: Handle<Texture>,
 | 
					        texture: Handle<Texture>,
 | 
				
			||||||
        size: Vec2,
 | 
					        size: Vec2,
 | 
				
			||||||
        columns: usize,
 | 
					        columns: usize,
 | 
				
			||||||
        rows: usize,
 | 
					        rows: usize,
 | 
				
			||||||
    ) -> SpriteSheet {
 | 
					    ) -> TextureAtlas {
 | 
				
			||||||
        let sprite_width = size.x() / columns as f32;
 | 
					        let texture_width = size.x() / columns as f32;
 | 
				
			||||||
        let sprite_height = size.y() / rows as f32;
 | 
					        let texture_height = size.y() / rows as f32;
 | 
				
			||||||
        let mut sprites = Vec::new();
 | 
					        let mut sprites = Vec::new();
 | 
				
			||||||
        for y in 0..rows {
 | 
					        for y in 0..rows {
 | 
				
			||||||
            for x in 0..columns {
 | 
					            for x in 0..columns {
 | 
				
			||||||
                sprites.push(Rect {
 | 
					                sprites.push(Rect {
 | 
				
			||||||
                    min: Vec2::new(x as f32 * sprite_width, y as f32 * sprite_height),
 | 
					                    min: Vec2::new(x as f32 * texture_width, y as f32 * texture_height),
 | 
				
			||||||
                    max: Vec2::new(
 | 
					                    max: Vec2::new(
 | 
				
			||||||
                        (x + 1) as f32 * sprite_width,
 | 
					                        (x + 1) as f32 * texture_width,
 | 
				
			||||||
                        (y + 1) as f32 * sprite_height,
 | 
					                        (y + 1) as f32 * texture_height,
 | 
				
			||||||
                    ),
 | 
					                    ),
 | 
				
			||||||
                })
 | 
					                })
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        SpriteSheet {
 | 
					        TextureAtlas {
 | 
				
			||||||
            dimensions: size,
 | 
					            dimensions: size,
 | 
				
			||||||
            sprites,
 | 
					            textures: sprites,
 | 
				
			||||||
            texture,
 | 
					            texture,
 | 
				
			||||||
 | 
					            texture_handles: None,
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn get_texture_index(&self, texture: Handle<Texture>) -> Option<usize> {
 | 
				
			||||||
 | 
					        self.texture_handles
 | 
				
			||||||
 | 
					            .as_ref()
 | 
				
			||||||
 | 
					            .and_then(|texture_handles| texture_handles.get(&texture).cloned())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
							
								
								
									
										88
									
								
								crates/bevy_sprite/src/texture_atlas_builder.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								crates/bevy_sprite/src/texture_atlas_builder.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,88 @@
 | 
				
			|||||||
 | 
					use crate::{TextureAtlas, Rect};
 | 
				
			||||||
 | 
					use bevy_asset::{Assets, Handle};
 | 
				
			||||||
 | 
					use bevy_render::texture::Texture;
 | 
				
			||||||
 | 
					use glam::Vec2;
 | 
				
			||||||
 | 
					use guillotiere::{size2, Allocation, AtlasAllocator};
 | 
				
			||||||
 | 
					use std::collections::HashMap;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub struct TextureAtlasBuilder {
 | 
				
			||||||
 | 
					    pub texture_allocations: Vec<(Handle<Texture>, Allocation)>,
 | 
				
			||||||
 | 
					    pub atlas_allocator: AtlasAllocator,
 | 
				
			||||||
 | 
					    pub texture: Texture,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Default for TextureAtlasBuilder {
 | 
				
			||||||
 | 
					    fn default() -> Self {
 | 
				
			||||||
 | 
					        Self::new(Vec2::new(256., 256.))
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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;
 | 
				
			||||||
 | 
					        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),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn add_texture(&mut self, texture_handle: Handle<Texture>, 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();
 | 
				
			||||||
 | 
					        let rect = allocation.rectangle;
 | 
				
			||||||
 | 
					        let atlas_width = self.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() {
 | 
				
			||||||
 | 
					            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;
 | 
				
			||||||
 | 
					            self.texture.data[begin..end]
 | 
				
			||||||
 | 
					                .copy_from_slice(&texture.data[texture_begin..texture_end]);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        self.texture_allocations.push((texture_handle, allocation));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn remove_texture(&mut self, texture_handle: Handle<Texture>) {
 | 
				
			||||||
 | 
					        if let Some(position) = self.texture_allocations.iter().position(|(handle, _)| *handle == texture_handle) {
 | 
				
			||||||
 | 
					            let (_, allocation) = self.texture_allocations.remove(position);
 | 
				
			||||||
 | 
					            self.atlas_allocator.deallocate(allocation.id);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn finish(self, textures: &mut Assets<Texture>) -> TextureAtlas {
 | 
				
			||||||
 | 
					        let mut texture_rects = Vec::with_capacity(self.texture_allocations.len());
 | 
				
			||||||
 | 
					        let mut texture_handles = HashMap::with_capacity(self.texture_allocations.len());
 | 
				
			||||||
 | 
					        for (index, (handle, allocation)) in self.texture_allocations.iter().enumerate() {
 | 
				
			||||||
 | 
					            texture_rects.push(allocation.rectangle.into());
 | 
				
			||||||
 | 
					            texture_handles.insert(*handle, index);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        TextureAtlas {
 | 
				
			||||||
 | 
					            dimensions: to_vec2(self.atlas_allocator.size()),
 | 
				
			||||||
 | 
					            texture: textures.add(self.texture),
 | 
				
			||||||
 | 
					            textures: texture_rects,
 | 
				
			||||||
 | 
					            texture_handles: Some(texture_handles),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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_vec2(size: guillotiere::Size) -> Vec2 {
 | 
				
			||||||
 | 
					    Vec2::new(size.width as f32, size.height as f32)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
@ -9,14 +9,14 @@ fn main() {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn animate_sprite_system(
 | 
					fn animate_sprite_system(
 | 
				
			||||||
    sprite_sheets: Res<Assets<SpriteSheet>>,
 | 
					    texture_atlases: Res<Assets<TextureAtlas>>,
 | 
				
			||||||
    mut timer: ComMut<Timer>,
 | 
					    mut timer: ComMut<Timer>,
 | 
				
			||||||
    mut sprite: ComMut<SpriteSheetSprite>,
 | 
					    mut sprite: ComMut<TextureAtlasSprite>,
 | 
				
			||||||
    sprite_sheet_handle: Com<Handle<SpriteSheet>>,
 | 
					    texture_atlas_handle: Com<Handle<TextureAtlas>>,
 | 
				
			||||||
) {
 | 
					) {
 | 
				
			||||||
    if timer.finished {
 | 
					    if timer.finished {
 | 
				
			||||||
        let sprite_sheet = sprite_sheets.get(&sprite_sheet_handle).unwrap();
 | 
					        let texture_atlas = texture_atlases.get(&texture_atlas_handle).unwrap();
 | 
				
			||||||
        sprite.index = ((sprite.index as usize + 1) % sprite_sheet.sprites.len()) as u32;
 | 
					        sprite.index = ((sprite.index as usize + 1) % texture_atlas.textures.len()) as u32;
 | 
				
			||||||
        timer.reset();
 | 
					        timer.reset();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -25,21 +25,21 @@ fn setup(
 | 
				
			|||||||
    command_buffer: &mut CommandBuffer,
 | 
					    command_buffer: &mut CommandBuffer,
 | 
				
			||||||
    asset_server: Res<AssetServer>,
 | 
					    asset_server: Res<AssetServer>,
 | 
				
			||||||
    mut textures: ResMut<Assets<Texture>>,
 | 
					    mut textures: ResMut<Assets<Texture>>,
 | 
				
			||||||
    mut sprite_sheets: ResMut<Assets<SpriteSheet>>,
 | 
					    mut texture_atlases: ResMut<Assets<TextureAtlas>>,
 | 
				
			||||||
) {
 | 
					) {
 | 
				
			||||||
    env_logger::init();
 | 
					    env_logger::init();
 | 
				
			||||||
    let texture_handle = asset_server
 | 
					    let texture_handle = asset_server
 | 
				
			||||||
        .load_sync(&mut textures, "assets/textures/rpg/chars/gabe/gabe-idle-run.png")
 | 
					        .load_sync(&mut textures, "assets/textures/rpg/chars/gabe/gabe-idle-run.png")
 | 
				
			||||||
        .unwrap();
 | 
					        .unwrap();
 | 
				
			||||||
    let texture = textures.get(&texture_handle).unwrap();
 | 
					    let texture = textures.get(&texture_handle).unwrap();
 | 
				
			||||||
    let sprite_sheet = SpriteSheet::from_grid(texture_handle, texture.size, 7, 1);
 | 
					    let texture_atlas = TextureAtlas::from_grid(texture_handle, texture.size, 7, 1);
 | 
				
			||||||
    let sprite_sheet_handle = sprite_sheets.add(sprite_sheet);
 | 
					    let texture_atlas_handle = texture_atlases.add(texture_atlas);
 | 
				
			||||||
    command_buffer
 | 
					    command_buffer
 | 
				
			||||||
        .build()
 | 
					        .build()
 | 
				
			||||||
        .add_entity(OrthographicCameraEntity::default())
 | 
					        .add_entity(OrthographicCameraEntity::default())
 | 
				
			||||||
        .add_entity(SpriteSheetEntity {
 | 
					        .add_entity(SpriteSheetEntity {
 | 
				
			||||||
            sprite_sheet: sprite_sheet_handle,
 | 
					            texture_atlas: texture_atlas_handle,
 | 
				
			||||||
            sprite: SpriteSheetSprite {
 | 
					            sprite: TextureAtlasSprite {
 | 
				
			||||||
                index: 0,
 | 
					                index: 0,
 | 
				
			||||||
                scale: 6.0,
 | 
					                scale: 6.0,
 | 
				
			||||||
                position: Vec3::new(0.0, 0.0, 0.0),
 | 
					                position: Vec3::new(0.0, 0.0, 0.0),
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										79
									
								
								examples/2d/texture_atlas.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								examples/2d/texture_atlas.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,79 @@
 | 
				
			|||||||
 | 
					use bevy::prelude::*;
 | 
				
			||||||
 | 
					use bevy_asset::{HandleId, LoadState};
 | 
				
			||||||
 | 
					use bevy_sprite::TextureAtlasBuilder;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn main() {
 | 
				
			||||||
 | 
					    App::build()
 | 
				
			||||||
 | 
					        .init_resource::<RpgSpriteHandles>()
 | 
				
			||||||
 | 
					        .init_resource::<State>()
 | 
				
			||||||
 | 
					        .add_default_plugins()
 | 
				
			||||||
 | 
					        .add_startup_system(setup.system())
 | 
				
			||||||
 | 
					        .add_system(load_atlas.system())
 | 
				
			||||||
 | 
					        .run();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Default)]
 | 
				
			||||||
 | 
					pub struct RpgSpriteHandles {
 | 
				
			||||||
 | 
					    handles: Vec<HandleId>,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn setup(
 | 
				
			||||||
 | 
					    command_buffer: &mut CommandBuffer,
 | 
				
			||||||
 | 
					    mut rpg_sprite_handles: ResMut<RpgSpriteHandles>,
 | 
				
			||||||
 | 
					    asset_server: Res<AssetServer>,
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
					    rpg_sprite_handles.handles = asset_server
 | 
				
			||||||
 | 
					        .load_asset_folder("assets/textures/rpg")
 | 
				
			||||||
 | 
					        .unwrap();
 | 
				
			||||||
 | 
					    command_buffer
 | 
				
			||||||
 | 
					        .build()
 | 
				
			||||||
 | 
					        .add_entity(OrthographicCameraEntity::default());
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Default)]
 | 
				
			||||||
 | 
					struct State {
 | 
				
			||||||
 | 
					    atlas_loaded: bool,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn load_atlas(
 | 
				
			||||||
 | 
					    command_buffer: &mut CommandBuffer,
 | 
				
			||||||
 | 
					    mut state: ResMut<State>,
 | 
				
			||||||
 | 
					    rpg_sprite_handles: Res<RpgSpriteHandles>,
 | 
				
			||||||
 | 
					    asset_server: Res<AssetServer>,
 | 
				
			||||||
 | 
					    mut texture_atlases: ResMut<Assets<TextureAtlas>>,
 | 
				
			||||||
 | 
					    mut textures: ResMut<Assets<Texture>>,
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
					    if state.atlas_loaded {
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let mut texture_atlas_builder = TextureAtlasBuilder::new(Vec2::new(600., 600.));
 | 
				
			||||||
 | 
					    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);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let texture_atlas = texture_atlas_builder.finish(&mut textures);
 | 
				
			||||||
 | 
					        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,
 | 
				
			||||||
 | 
					                ..Default::default()
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            texture_atlas: atlas_handle,
 | 
				
			||||||
 | 
					            ..Default::default()
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        state.atlas_loaded = true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -24,13 +24,13 @@ fn setup(
 | 
				
			|||||||
    //     ..Default::default()
 | 
					    //     ..Default::default()
 | 
				
			||||||
    // });
 | 
					    // });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let texture = Texture::load(TextureType::Png(
 | 
					    let texture_handle = asset_server
 | 
				
			||||||
        "assets/branding/bevy_logo_dark_big.png".to_string(),
 | 
					        .load_sync(&mut textures, "assets/branding/bevy_logo_dark_big.png")
 | 
				
			||||||
    ));
 | 
					        .unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let font_handle = asset_server.load("assets/fonts/FiraSans-Bold.ttf").unwrap();
 | 
					    let font_handle = asset_server.load("assets/fonts/FiraSans-Bold.ttf").unwrap();
 | 
				
			||||||
 | 
					    let texture = textures.get(&texture_handle).unwrap();
 | 
				
			||||||
    let aspect = texture.aspect();
 | 
					    let aspect = texture.aspect();
 | 
				
			||||||
    let texture_handle = textures.add(texture);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let blue_material_handle = materials.add(Color::rgb(0.6, 0.6, 1.0).into());
 | 
					    let blue_material_handle = materials.add(Color::rgb(0.6, 0.6, 1.0).into());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -33,7 +33,7 @@ pub use crate::{
 | 
				
			|||||||
    scene::{Scene, SceneSpawner},
 | 
					    scene::{Scene, SceneSpawner},
 | 
				
			||||||
    sprite::{
 | 
					    sprite::{
 | 
				
			||||||
        entity::{SpriteEntity, SpriteSheetEntity},
 | 
					        entity::{SpriteEntity, SpriteSheetEntity},
 | 
				
			||||||
        ColorMaterial, Quad, Sprite, SpriteSheet, SpriteSheetSprite,
 | 
					        ColorMaterial, Quad, Sprite, TextureAtlas, TextureAtlasSprite,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    text::Font,
 | 
					    text::Font,
 | 
				
			||||||
    transform::prelude::*,
 | 
					    transform::prelude::*,
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user