diff --git a/Cargo.toml b/Cargo.toml index b4bbb51..fd01bec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,14 +1,14 @@ [package] -name = "bevy_proto_aseprite" +name = "bevy_asefile" version = "0.1.0" -authors = ["alpine_alpaca "] +authors = ["alpine_alpaca ", "B-Reif"] edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] bevy = "0.5" -asefile = "0.2" +asefile = { git = "https://github.com/B-Reif/asefile", branch = "main" } anyhow = "1.0" [dev-dependencies] diff --git a/README.md b/README.md index bd3f3d0..697a8f1 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -Basic tooling to use Aseprite animations with Bevy. +Basic tooling to use Aseprite animations with Bevy. Forked from bevy_proto_aseprite. # Example diff --git a/examples/simple/ids.rs b/examples/simple/ids.rs index 0ad86e8..c83f67f 100644 --- a/examples/simple/ids.rs +++ b/examples/simple/ids.rs @@ -1,4 +1,4 @@ -use bevy_proto_aseprite::anim_id::AnimationId; +use bevy_asefile::anim_id::AnimationId; use strum::{EnumIter, EnumProperty, IntoEnumIterator}; #[derive(Debug, Clone, PartialEq, Eq, Hash, EnumProperty, EnumIter)] diff --git a/examples/simple/main.rs b/examples/simple/main.rs index 3d9a9d3..bf93259 100644 --- a/examples/simple/main.rs +++ b/examples/simple/main.rs @@ -1,7 +1,7 @@ use std::path::Path; use bevy::{input::system::exit_on_esc_system, prelude::*}; -use bevy_proto_aseprite::{ +use bevy_asefile::{ self, anim_id::{AnimationById, AnimationId}, animate::{self, Animation, AnimationInfo}, @@ -65,7 +65,7 @@ pub fn spawn_sprites(mut commands: Commands, anim_ids: Res //commands.spawn_bundle(OrthographicCameraBundle::new_2d()); commands.spawn_bundle({ let mut b = OrthographicCameraBundle::new_2d(); - b.orthographic_projection.scale = 1.0 / 3.0; // scale to 3x + b.orthographic_projection.scale = 1.0 / 3.0; // scale to 3x b }); diff --git a/src/animation.rs b/src/animation.rs new file mode 100644 index 0000000..fcb1490 --- /dev/null +++ b/src/animation.rs @@ -0,0 +1,29 @@ +use asefile::{AsepriteFile, Tag}; +use std::path::PathBuf; + +#[derive(Debug)] +pub(crate) struct Animation { + pub(crate) file: PathBuf, + pub(crate) tag: Option, + pub(crate) sprites: Vec, +} +impl Animation { + pub(crate) fn new(name: &PathBuf, ase: &AsepriteFile, sprite_offset: usize) -> Self { + Self { + file: name.clone(), + tag: None, + sprites: (0..ase.num_frames()) + .map(|f| sprite_offset + f as usize) + .collect(), + } + } + pub(crate) fn from_tag(name: &PathBuf, sprite_offset: usize, tag: &Tag) -> Self { + Animation { + file: name.clone(), + tag: Some(tag.name().to_owned()), + sprites: (tag.from_frame()..tag.to_frame() + 1) + .map(|f| sprite_offset + f as usize) + .collect(), + } + } +} diff --git a/src/ase.rs b/src/ase.rs new file mode 100644 index 0000000..05862ff --- /dev/null +++ b/src/ase.rs @@ -0,0 +1,50 @@ +use std::{fmt, path::PathBuf}; + +use asefile::AsepriteFile; +use bevy::utils::HashMap; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct AseId(u32); +impl AseId { + pub fn new(inner: u32) -> Self { + Self(inner) + } + pub fn inner(&self) -> &u32 { + &self.0 + } +} +impl fmt::Display for AseId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "AseId({})", self.0) + } +} + +pub(crate) struct AseKeyed { + pub(crate) id: AseId, + pub(crate) path: PathBuf, + pub(crate) file: AsepriteFile, +} + +pub(crate) struct AsesById(HashMap); +impl AsesById { + pub(crate) fn inner(&self) -> &HashMap { + &self.0 + } + pub fn iter(&self) -> std::collections::hash_map::Iter<'_, AseId, AseKeyed> { + self.0.iter() + } +} +impl From> for AsesById { + fn from(vec: Vec<(PathBuf, AsepriteFile)>) -> Self { + Self( + vec.into_iter() + .enumerate() + .map(|(idx, (path, file))| { + let id = AseId::new(idx as u32); + let value = AseKeyed { id, path, file }; + (id, value) + }) + .collect(), + ) + } +} diff --git a/src/aseloader.rs b/src/aseloader.rs index ed97dec..b54fbf4 100644 --- a/src/aseloader.rs +++ b/src/aseloader.rs @@ -1,5 +1,6 @@ use crate::animate::{Animation, AnimationInfo, Frame, Sprite}; -use asefile::{AsepriteFile, Tag}; +use crate::processing; +use asefile::{AsepriteFile, Tag, Tileset, TilesetId}; use bevy::{ asset::{AssetLoader, BoxedFuture, LoadState, LoadedAsset}, prelude::*, @@ -69,7 +70,7 @@ impl AssetLoader for AsepriteAssetLoader { // #[derive(Debug)] pub struct AsepriteLoader { todo_handles: Vec>, - done: Arc>>>, + done: Arc>>>, in_progress: Arc, } @@ -128,8 +129,7 @@ impl AsepriteLoader { let output = self.done.clone(); let task = pool.spawn(async move { - let processed = create_animations(inputs); - // println!("Batch finished"); + let processed = processing::AseAssets::::new(inputs); let mut out = output.lock().unwrap(); out.push(processed); }); @@ -160,12 +160,8 @@ impl AsepriteLoader { finish_animations(r, animations, anim_info, textures, atlases); self.in_progress.fetch_sub(1, Ordering::SeqCst); } - - // println!("Need to process results: {}", results.len()); } - // fn create_textures(&mut self, pool: &AsyncComputeTaskPool) {} - pub fn check_pending(&self) -> u32 { self.in_progress.load(Ordering::SeqCst) } @@ -175,32 +171,8 @@ impl AsepriteLoader { } } -fn create_animations(ases: Vec<(PathBuf, AsepriteFile)>) -> ProcessedAse { - let mut tmp_sprites: Vec> = Vec::new(); - let mut tmp_anim_info: Vec = Vec::new(); - for (name, ase) in &ases { - debug!("Processing Aseprite file: {}", name.display()); - let sprite_offset = tmp_sprites.len(); - - for frame in 0..ase.num_frames() { - tmp_sprites.push(TempSpriteInfo::::new(ase, frame)); - } - - tmp_anim_info.push(TempAnimInfo::new(name, ase, sprite_offset)); - - for tag_id in 0..ase.num_tags() { - let tag = ase.tag(tag_id); - tmp_anim_info.push(TempAnimInfo::from_tag(name, sprite_offset, tag)); - } - } - ProcessedAse { - sprites: tmp_sprites, - anims: tmp_anim_info, - } -} - fn finish_animations( - input: ProcessedAse, + input: processing::AseAssets, animations: &mut Assets, anim_info: &mut AnimationInfo, textures: &mut Assets, @@ -209,20 +181,20 @@ fn finish_animations( let mut texture_atlas_builder = TextureAtlasBuilder::default(); let start = Instant::now(); - let tmp_sprites: Vec>> = input + let tmp_sprites: Vec>> = input .sprites .into_iter() .map( - |TempSpriteInfo { + |processing::Sprite { frame, - tex, + texture: tex, duration, }| { let tex_handle = textures.add(tex); let texture = textures.get(&tex_handle).unwrap(); texture_atlas_builder.add_texture(tex_handle.clone_weak(), texture); - TempSpriteInfo { - tex: tex_handle, + processing::Sprite { + texture: tex_handle, frame, duration, } @@ -240,7 +212,7 @@ fn finish_animations( let mut frames = Vec::with_capacity(tmp_anim.sprites.len()); for sprite_id in tmp_anim.sprites { let tmp_sprite = &tmp_sprites[sprite_id]; - let atlas_index = atlas.get_texture_index(&tmp_sprite.tex).unwrap(); + let atlas_index = atlas.get_texture_index(&tmp_sprite.texture).unwrap(); frames.push(Frame { sprite: Sprite { atlas: atlas_handle.clone(), @@ -254,67 +226,6 @@ fn finish_animations( } } -struct ProcessedAse { - sprites: Vec>, - anims: Vec, -} - -// #[derive(Debug)] -struct TempSpriteInfo { - // file: PathBuf, - frame: u32, - tex: T, - duration: u32, -} -impl TempSpriteInfo { - fn new(ase: &AsepriteFile, frame: u32) -> Self { - let img = ase.frame(frame).image(); - let size = Extent3d { - width: ase.width() as u32, - height: ase.height() as u32, - depth: 1, - }; - let texture = Texture::new_fill( - size, - TextureDimension::D2, - img.as_raw(), - TextureFormat::Rgba8UnormSrgb, - ); - Self { - frame, - tex: texture, - duration: ase.frame(frame).duration(), - } - } -} - -#[derive(Debug)] -struct TempAnimInfo { - file: PathBuf, - tag: Option, - sprites: Vec, -} -impl TempAnimInfo { - fn new(name: &PathBuf, ase: &AsepriteFile, sprite_offset: usize) -> Self { - Self { - file: name.clone(), - tag: None, - sprites: (0..ase.num_frames()) - .map(|f| sprite_offset + f as usize) - .collect(), - } - } - fn from_tag(name: &PathBuf, sprite_offset: usize, tag: &Tag) -> Self { - TempAnimInfo { - file: name.clone(), - tag: Some(tag.name().to_owned()), - sprites: (tag.from_frame()..tag.to_frame() + 1) - .map(|f| sprite_offset + f as usize) - .collect(), - } - } -} - pub fn aseprite_loader( mut loader: ResMut, task_pool: ResMut, diff --git a/src/framedata.rs b/src/framedata.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/lib.rs b/src/lib.rs index 2c6cbad..e49e9f0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,11 @@ pub mod anim_id; pub mod animate; +mod animation; +mod ase; pub mod aseloader; +mod processing; +mod sprite; +mod tileset; pub mod timer; #[cfg(test)] @@ -10,3 +15,5 @@ mod tests { assert_eq!(2 + 2, 4); } } + +pub use tileset::{TileSize, Tileset, TilesetId}; diff --git a/src/processing.rs b/src/processing.rs new file mode 100644 index 0000000..5ec006b --- /dev/null +++ b/src/processing.rs @@ -0,0 +1,99 @@ +use crate::{ + animation::Animation, + ase::{AseKeyed, AsesById}, + tileset::{self, TilesetKey, TilesetResult, TilesetsById}, + Tileset, +}; +use asefile::AsepriteFile; +use bevy::{ + prelude::*, + render::texture::{Extent3d, TextureDimension, TextureFormat}, +}; +use std::{collections::HashMap, path::PathBuf}; + +pub(crate) struct TilesetsByKey(HashMap>); +impl TilesetsByKey { + fn new() -> Self { + Self(HashMap::new()) + } + fn add_ase(&mut self, ase: &AseKeyed) -> TilesetResult<()> { + let ase_id = &ase.id; + let kv_pairs: TilesetResult>> = ase + .file + .tilesets() + .map() + .values() + .map(|ts| { + let value = Tileset::::from_ase_with_texture(&ase.file, ts)?; + let key = TilesetKey::new(ase_id, value.id()); + Ok((key, value)) + }) + .collect(); + self.0.extend(kv_pairs?); + Ok(()) + } +} + +pub(crate) struct AseAssets { + pub(crate) sprites: Vec>, + pub(crate) anims: Vec, + pub(crate) tilesets: TilesetsByKey, +} +impl AseAssets { + pub(crate) fn new(ases: Vec<(PathBuf, AsepriteFile)>) -> Self { + let ases_by_id = AsesById::from(ases); + let mut tmp_sprites: Vec> = Vec::new(); + let mut tmp_anim_info: Vec = Vec::new(); + let mut tilesets = TilesetsByKey::::new(); + for (_id, ase_keyed) in ases_by_id.iter() { + tilesets.add_ase(ase_keyed); + } + // for (name, ase) in &ases { + // debug!("Processing Aseprite file: {}", name.display()); + // let sprite_offset = tmp_sprites.len(); + + // for frame in 0..ase.num_frames() { + // tmp_sprites.push(Sprite::::new(ase, frame)); + // } + + // tmp_anim_info.push(Animation::new(name, ase, sprite_offset)); + + // for tag_id in 0..ase.num_tags() { + // let tag = ase.tag(tag_id); + // tmp_anim_info.push(Animation::from_tag(name, sprite_offset, tag)); + // } + // } + Self { + sprites: tmp_sprites, + anims: tmp_anim_info, + tilesets, + } + } +} + +pub(crate) struct Sprite { + pub(crate) frame: u32, + pub(crate) texture: T, + pub(crate) duration: u32, +} +impl Sprite { + pub(crate) fn new(ase: &AsepriteFile, frame: u32) -> Self { + let img = ase.frame(frame).image(); + let size = Extent3d { + width: ase.width() as u32, + height: ase.height() as u32, + depth: 1, + }; + let texture = Texture::new_fill( + size, + TextureDimension::D2, + img.as_raw(), + TextureFormat::Rgba8UnormSrgb, + ); + Self { + frame, + texture, + duration: ase.frame(frame).duration(), + } + } +} diff --git a/src/sprite.rs b/src/sprite.rs new file mode 100644 index 0000000..a34b8fa --- /dev/null +++ b/src/sprite.rs @@ -0,0 +1,32 @@ +use asefile::AsepriteFile; +use bevy::{ + prelude::Texture, + render::texture::{Extent3d, TextureDimension, TextureFormat}, +}; + +pub(crate) struct Sprite { + pub(crate) frame: u32, + pub(crate) texture: T, + pub(crate) duration: u32, +} +impl Sprite { + pub(crate) fn new(ase: &AsepriteFile, frame: u32) -> Self { + let img = ase.frame(frame).image(); + let size = Extent3d { + width: ase.width() as u32, + height: ase.height() as u32, + depth: 1, + }; + let texture = Texture::new_fill( + size, + TextureDimension::D2, + img.as_raw(), + TextureFormat::Rgba8UnormSrgb, + ); + Self { + frame, + texture, + duration: ase.frame(frame).duration(), + } + } +} diff --git a/src/tileset.rs b/src/tileset.rs new file mode 100644 index 0000000..26a9216 --- /dev/null +++ b/src/tileset.rs @@ -0,0 +1,207 @@ +use asefile::{AsepriteFile, TilesetImageError}; +use bevy::{ + prelude::*, + render::texture::{Extent3d, TextureDimension, TextureFormat}, +}; +use std::{collections::HashMap, fmt, iter::FromIterator}; + +use crate::ase::AseId; + +pub(crate) type TilesetResult = std::result::Result; + +#[derive(Debug)] +pub enum TilesetError { + MissingId(TilesetId), + NoPixels(TilesetId), +} +impl fmt::Display for TilesetError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + TilesetError::MissingId(tileset_id) => { + write!(f, "No tileset found with id: {}", tileset_id) + } + TilesetError::NoPixels(tileset_id) => { + write!(f, "No pixel data for tileset with id: {}", tileset_id) + } + } + } +} +impl From<&TilesetImageError> for TilesetError { + fn from(e: &TilesetImageError) -> Self { + match e { + TilesetImageError::MissingTilesetId(id) => Self::MissingId(id.into()), + TilesetImageError::NoPixelsInTileset(id) => Self::NoPixels(id.into()), + } + } +} +impl From for TilesetError { + fn from(e: TilesetImageError) -> Self { + Self::from(&e) + } +} + +fn texture_from(ase: &AsepriteFile, tileset: &asefile::Tileset) -> TilesetResult { + let tileset_id = tileset.id(); + let image = ase.tileset_image(&tileset_id)?; + let size = Extent3d { + width: image.width(), + height: image.height(), + depth: 1, + }; + Ok(Texture::new_fill( + size, + TextureDimension::D2, + image.as_raw(), + TextureFormat::Rgba8UnormSrgb, + )) +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct TilesetId(u32); +impl TilesetId { + pub fn new(inner: u32) -> Self { + Self(inner) + } + pub fn inner(&self) -> &u32 { + &self.0 + } + pub fn into_inner(self) -> u32 { + self.0 + } +} +impl From<&asefile::TilesetId> for TilesetId { + fn from(ase_id: &asefile::TilesetId) -> Self { + Self(*ase_id.value()) + } +} +impl fmt::Display for TilesetId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "TilesetId({})", self.0) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct TilesetKey { + ase_id: AseId, + tileset_id: TilesetId, +} +impl TilesetKey { + pub fn new(ase_id: &AseId, tileset_id: &TilesetId) -> Self { + Self { + ase_id: *ase_id, + tileset_id: *tileset_id, + } + } + pub fn ase_id(&self) -> &AseId { + &self.ase_id + } + pub fn tileset_id(&self) -> &TilesetId { + &self.tileset_id + } +} +impl fmt::Display for TilesetKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "TilesetKey(ase_id {}, tileset_id {})", + self.ase_id, self.tileset_id + ) + } +} + +#[derive(Debug)] +pub struct TileSize { + width: u16, + height: u16, +} +impl TileSize { + fn from_ase(ase_size: &asefile::TileSize) -> Self { + Self { + width: *ase_size.width(), + height: *ase_size.height(), + } + } + pub fn width(&self) -> &u16 { + &self.width + } + pub fn height(&self) -> &u16 { + &self.height + } +} + +#[derive(Debug)] +pub struct Tileset { + id: TilesetId, + tile_count: u32, + tile_size: TileSize, + name: String, + texture: T, +} +impl Tileset { + pub fn id(&self) -> &TilesetId { + &self.id + } + pub fn size(&self) -> &TileSize { + &self.tile_size + } + pub fn tile_count(&self) -> &u32 { + &self.tile_count + } + pub fn string(&self) -> &String { + &self.name + } + pub fn texture(&self) -> &T { + &self.texture + } + fn from_ase(f: F, ase: &AsepriteFile, ase_tileset: &asefile::Tileset) -> TilesetResult + where + F: FnOnce(&AsepriteFile, &asefile::Tileset) -> TilesetResult, + { + let ase_id = *ase_tileset.id(); + let texture = f(ase, ase_tileset)?; + let ase_size = ase_tileset.tile_size(); + Ok(Self { + id: TilesetId(*ase_id.value()), + tile_count: *ase_tileset.tile_count(), + tile_size: TileSize::from_ase(ase_size), + name: ase_tileset.name().to_string(), + texture, + }) + } +} +impl Tileset { + pub(crate) fn from_ase_with_texture( + ase: &AsepriteFile, + ase_tileset: &asefile::Tileset, + ) -> TilesetResult { + Tileset::::from_ase(texture_from, ase, ase_tileset) + } +} + +#[derive(Debug)] +pub(crate) struct TilesetsById(HashMap>); +impl TilesetsById { + pub fn hash_map(&self) -> &HashMap> { + &self.0 + } + pub fn get(&self, id: TilesetId) -> Option<&Tileset> { + self.0.get(&id) + } +} +impl TilesetsById { + pub(crate) fn from_ase(ase: &AsepriteFile) -> TilesetResult { + ase.tilesets() + .map() + .values() + .map(|ts| { + let ts = Tileset::::from_ase_with_texture(ase, ts)?; + Ok((ts.id, ts)) + }) + .collect() + } +} +impl FromIterator<(TilesetId, Tileset)> for TilesetsById { + fn from_iter)>>(iter: I) -> Self { + Self(iter.into_iter().collect()) + } +}