use ron::de::from_bytes as from_ron_bytes;
use amethyst_assets::{
Asset, Error as AssetsError, ErrorKind as AssetsErrorKind, Handle, ProcessingState,
Result as AssetsResult, SimpleFormat,
};
use amethyst_core::specs::prelude::{Component, DenseVecStorage, VecStorage};
use crate::Texture;
pub type SpriteSheetHandle = Handle<SpriteSheet>;
#[derive(Clone, Debug, PartialEq)]
pub struct SpriteSheet {
pub texture: Handle<Texture>,
pub sprites: Vec<Sprite>,
}
impl Asset for SpriteSheet {
const NAME: &'static str = "renderer::SpriteSheet";
type Data = Self;
type HandleStorage = VecStorage<Handle<Self>>;
}
impl From<SpriteSheet> for AssetsResult<ProcessingState<SpriteSheet>> {
fn from(sprite_sheet: SpriteSheet) -> AssetsResult<ProcessingState<SpriteSheet>> {
Ok(ProcessingState::Loaded(sprite_sheet))
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum Flipped {
None,
Horizontal,
Vertical,
Both,
}
impl Component for Flipped {
type Storage = DenseVecStorage<Self>;
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Sprite {
pub width: f32,
pub height: f32,
pub offsets: [f32; 2],
pub tex_coords: TextureCoordinates,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct TextureCoordinates {
pub left: f32,
pub right: f32,
pub bottom: f32,
pub top: f32,
}
impl Sprite {
pub fn from_pixel_values(
image_w: u32,
image_h: u32,
sprite_w: u32,
sprite_h: u32,
pixel_left: u32,
pixel_top: u32,
offsets: [f32; 2],
) -> Sprite {
let image_w = image_w as f32;
let image_h = image_h as f32;
let offsets = [offsets[0] as f32, offsets[1] as f32];
let pixel_right = (pixel_left + sprite_w) as f32;
let pixel_bottom = (pixel_top + sprite_h) as f32;
let pixel_left = pixel_left as f32;
let pixel_top = pixel_top as f32;
let left = (pixel_left) / image_w;
let right = (pixel_right) / image_w;
let top = (image_h - pixel_top) / image_h;
let bottom = (image_h - pixel_bottom) / image_h;
let tex_coords = TextureCoordinates {
left,
right,
top,
bottom,
};
Sprite {
width: sprite_w as f32,
height: sprite_h as f32,
offsets,
tex_coords,
}
}
}
impl From<((f32, f32), [f32; 4])> for Sprite {
fn from((dimensions, tex_coords): ((f32, f32), [f32; 4])) -> Self {
Self::from((dimensions, [0.0; 2], tex_coords))
}
}
impl From<((f32, f32), [f32; 2], [f32; 4])> for Sprite {
fn from(((width, height), offsets, tex_coords): ((f32, f32), [f32; 2], [f32; 4])) -> Self {
Sprite {
width,
height,
offsets,
tex_coords: TextureCoordinates::from(tex_coords),
}
}
}
impl From<((f32, f32), (f32, f32))> for TextureCoordinates {
fn from(((left, right), (bottom, top)): ((f32, f32), (f32, f32))) -> Self {
TextureCoordinates {
left,
right,
bottom,
top,
}
}
}
impl From<[f32; 4]> for TextureCoordinates {
fn from(uv: [f32; 4]) -> Self {
TextureCoordinates {
left: uv[0],
right: uv[1],
bottom: uv[2],
top: uv[3],
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct SpriteRender {
pub sprite_sheet: SpriteSheetHandle,
pub sprite_number: usize,
}
impl Component for SpriteRender {
type Storage = VecStorage<Self>;
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
struct SpritePosition {
pub x: u32,
pub y: u32,
pub width: u32,
pub height: u32,
pub offsets: Option<[f32; 2]>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
struct SerializedSpriteSheet {
pub spritesheet_width: u32,
pub spritesheet_height: u32,
pub sprites: Vec<SpritePosition>,
}
#[derive(Clone, Deserialize, Serialize)]
pub struct SpriteSheetFormat;
impl SimpleFormat<SpriteSheet> for SpriteSheetFormat {
const NAME: &'static str = "SPRITE_SHEET";
type Options = Handle<Texture>;
fn import(&self, bytes: Vec<u8>, texture: Self::Options) -> AssetsResult<SpriteSheet> {
let sheet: SerializedSpriteSheet = from_ron_bytes(&bytes).map_err(|_| {
AssetsError::from_kind(AssetsErrorKind::Format(
"Failed to parse Ron file for SpriteSheet",
))
})?;
let mut sprites: Vec<Sprite> = Vec::with_capacity(sheet.sprites.len());
for sp in sheet.sprites {
let sprite = Sprite::from_pixel_values(
sheet.spritesheet_width as u32,
sheet.spritesheet_height as u32,
sp.width as u32,
sp.height as u32,
sp.x as u32,
sp.y as u32,
sp.offsets.unwrap_or([0.0; 2]),
);
sprites.push(sprite);
}
Ok(SpriteSheet { texture, sprites })
}
}
#[cfg(test)]
mod test {
use super::{Sprite, TextureCoordinates};
#[test]
fn texture_coordinates_from_tuple_maps_fields_correctly() {
assert_eq!(
TextureCoordinates {
left: 0.,
right: 0.5,
bottom: 0.75,
top: 1.0,
},
((0.0, 0.5), (0.75, 1.0)).into()
);
}
#[test]
fn texture_coordinates_from_slice_maps_fields_correctly() {
assert_eq!(
TextureCoordinates {
left: 0.,
right: 0.5,
bottom: 0.75,
top: 1.0,
},
[0.0, 0.5, 0.75, 1.0].into()
);
}
#[test]
fn sprite_from_tuple_maps_fields_correctly() {
assert_eq!(
Sprite {
width: 10.,
height: 40.,
offsets: [5., 20.],
tex_coords: TextureCoordinates {
left: 0.,
right: 0.5,
bottom: 0.75,
top: 1.0,
},
},
((10., 40.), [5., 20.], [0.0, 0.5, 0.75, 1.0]).into()
);
}
#[test]
fn sprite_offsets_default_to_zero() {
assert_eq!(
Sprite {
width: 10.,
height: 40.,
offsets: [0., 0.],
tex_coords: TextureCoordinates {
left: 0.,
right: 0.5,
bottom: 0.75,
top: 1.0,
},
},
((10., 40.), [0.0, 0.5, 0.75, 1.0]).into()
);
}
#[test]
fn sprite_from_pixel_values_calculates_pixel_perfect_coordinates() {
let image_w = 30;
let image_h = 40;
let sprite_w = 10;
let sprite_h = 20;
let pixel_left = 0;
let pixel_top = 20;
let offsets = [-5.0, -10.0];
assert_eq!(
Sprite::from((
(10., 20.), [-5., -10.], [0., 10. / 30., 0., 20. / 40.], )),
Sprite::from_pixel_values(
image_w, image_h, sprite_w, sprite_h, pixel_left, pixel_top, offsets
)
);
}
}