use figures::{Displayable, Pixels, Points, Scaled};
use crate::math::{Angle, ExtentsRect, Point, Rect, Size};
use crate::texture::Texture;
use crate::Error;
mod batch;
mod collection;
mod gpu_batch;
mod pipeline;
mod sheet;
pub(crate) use self::batch::Batch;
pub(crate) use self::gpu_batch::{BatchBuffers, GpuBatch};
pub(crate) use self::pipeline::Pipeline;
mod source;
use std::collections::HashMap;
use std::iter::IntoIterator;
use std::sync::Arc;
use std::time::Duration;
pub use self::collection::*;
pub use self::pipeline::VertexShaderSource;
pub use self::sheet::*;
pub use self::source::*;
#[macro_export]
macro_rules! include_aseprite_sprite {
($path:expr) => {{
$crate::include_texture!(concat!($path, ".png")).and_then(|texture| {
$crate::sprite::Sprite::load_aseprite_json(
include_str!(concat!($path, ".json")),
&texture,
)
})
}};
}
#[derive(Debug, Clone)]
pub enum AnimationMode {
Forward,
Reverse,
PingPong,
}
impl AnimationMode {
const fn default_direction(&self) -> AnimationDirection {
match self {
AnimationMode::Forward | AnimationMode::PingPong => AnimationDirection::Forward,
AnimationMode::Reverse => AnimationDirection::Reverse,
}
}
}
#[derive(Debug, Clone)]
enum AnimationDirection {
Forward,
Reverse,
}
#[derive(Debug, Clone)]
pub struct Sprite {
pub animations: SpriteAnimations,
elapsed_since_frame_change: Duration,
current_tag: Option<String>,
current_frame: usize,
current_animation_direction: AnimationDirection,
}
impl From<SpriteAnimations> for Sprite {
fn from(animations: SpriteAnimations) -> Self {
Self::new(animations)
}
}
impl Sprite {
#[must_use]
pub const fn new(animations: SpriteAnimations) -> Self {
Self {
animations,
current_frame: 0,
current_tag: None,
elapsed_since_frame_change: Duration::from_millis(0),
current_animation_direction: AnimationDirection::Forward,
}
}
#[must_use]
pub fn merged<S: Into<String>, I: IntoIterator<Item = (S, Self)>>(source: I) -> Self {
let mut combined = HashMap::new();
for (name, sprite) in source {
combined.insert(
Some(name.into()),
sprite
.animations
.animation_for(&Option::<&str>::None)
.unwrap()
.clone(),
);
}
Self::new(SpriteAnimations::new(combined))
}
#[must_use]
pub fn single_frame(texture: Texture) -> Self {
let source = SpriteSource::entire_texture(texture);
let mut frames = HashMap::new();
frames.insert(
None,
SpriteAnimation::new(vec![SpriteFrame {
source,
duration: None,
}])
.with_mode(AnimationMode::Forward),
);
let frames = SpriteAnimations::new(frames);
Self::new(frames)
}
#[allow(clippy::too_many_lines)]
pub fn load_aseprite_json(raw_json: &str, texture: &Texture) -> crate::Result<Self> {
let json = json::parse(raw_json)?;
let meta = &json["meta"];
if !meta.is_object() {
return Err(Error::SpriteParse(
"invalid aseprite json: No `meta` section".to_owned(),
));
}
let texture_size = texture.size();
if meta["size"]["w"] != texture_size.width || meta["size"]["h"] != texture_size.height {
return Err(Error::SpriteParse(
"invalid aseprite json: Size did not match input texture".to_owned(),
));
}
let mut frames = HashMap::new();
for (name, frame) in json["frames"].entries() {
let name = name.split('.').next().unwrap();
let name_parts = name.split(|c| c == '_' || c == ' ').collect::<Vec<_>>();
let frame_number = name_parts[name_parts.len() - 1]
.parse::<usize>()
.or_else(|_| {
if json["frames"].len() == 1 {
Ok(0)
} else {
Err(Error::SpriteParse(
"invalid aseprite json: frame was not numeric.".to_owned(),
))
}
})?;
let duration = match frame["duration"].as_u64() {
Some(millis) => Duration::from_millis(millis),
None => {
return Err(Error::SpriteParse(
"invalid aseprite json: invalid duration".to_owned(),
))
}
};
let frame = Rect::new(
Point::new(
frame["frame"]["x"].as_u32().ok_or_else(|| {
Error::SpriteParse(
"invalid aseprite json: frame x was not valid".to_owned(),
)
})?,
frame["frame"]["y"].as_u32().ok_or_else(|| {
Error::SpriteParse(
"invalid aseprite json: frame y was not valid".to_owned(),
)
})?,
),
Size::new(
frame["frame"]["w"].as_u32().ok_or_else(|| {
Error::SpriteParse(
"invalid aseprite json: frame w was not valid".to_owned(),
)
})?,
frame["frame"]["h"].as_u32().ok_or_else(|| {
Error::SpriteParse(
"invalid aseprite json: frame h was not valid".to_owned(),
)
})?,
),
);
let source = SpriteSource::new(frame, texture.clone());
frames.insert(
frame_number,
SpriteFrame {
duration: Some(duration),
source,
},
);
}
let mut animations = HashMap::new();
for tag in meta["frameTags"].members() {
let direction = if tag["direction"] == "forward" {
AnimationMode::Forward
} else if tag["direction"] == "reverse" {
AnimationMode::Reverse
} else if tag["direction"] == "pingpong" {
AnimationMode::PingPong
} else {
return Err(Error::SpriteParse(
"invalid aseprite json: frameTags direction is an unknown value".to_owned(),
));
};
let name = tag["name"].as_str().map(str::to_owned);
let start_frame = tag["from"].as_usize().ok_or_else(|| {
Error::SpriteParse(
"invalid aseprite json: frameTags from was not numeric".to_owned(),
)
})?;
let end_frame = tag["to"].as_usize().ok_or_else(|| {
Error::SpriteParse(
"invalid aseprite json: frameTags from was not numeric".to_owned(),
)
})?;
let mut animation_frames = Vec::new();
for i in start_frame..=end_frame {
let frame = frames.get(&i).ok_or_else(|| {
Error::SpriteParse(
"invalid aseprite json: frameTags frame was out of bounds".to_owned(),
)
})?;
animation_frames.push(frame.clone());
}
animations.insert(
name,
SpriteAnimation::new(animation_frames).with_mode(direction),
);
}
let mut frames: Vec<_> = frames.into_iter().collect();
frames.sort_by(|a, b| a.0.cmp(&b.0));
animations.insert(
None,
SpriteAnimation::new(frames.iter().map(|(_, f)| f.clone()).collect())
.with_mode(AnimationMode::Forward),
);
Ok(Self::new(SpriteAnimations::new(animations)))
}
pub fn set_current_tag<S: Into<String>>(&mut self, tag: Option<S>) -> crate::Result<()> {
let new_tag = tag.map(Into::into);
if self.current_tag != new_tag {
self.current_animation_direction = {
let animation = self
.animations
.animations
.get(&new_tag)
.ok_or(Error::InvalidSpriteTag)?;
animation.mode.default_direction()
};
self.current_frame = 0;
self.current_tag = new_tag;
}
Ok(())
}
#[must_use]
pub fn current_tag(&self) -> Option<&'_ str> {
self.current_tag.as_deref()
}
pub fn get_frame(&mut self, elapsed: Option<Duration>) -> crate::Result<SpriteSource> {
if let Some(elapsed) = elapsed {
self.elapsed_since_frame_change += elapsed;
let current_frame_duration = self.with_current_frame(|frame| frame.duration)?;
if let Some(frame_duration) = current_frame_duration {
if self.elapsed_since_frame_change > frame_duration {
self.elapsed_since_frame_change = Duration::from_nanos(
(self.elapsed_since_frame_change.as_nanos() % frame_duration.as_nanos())
as u64,
);
self.advance_frame()?;
}
}
}
self.current_frame()
}
#[inline]
pub fn current_frame(&self) -> crate::Result<SpriteSource> {
self.with_current_frame(|frame| frame.source.clone())
}
pub fn remaining_frame_duration(&self) -> crate::Result<Option<Duration>> {
let duration = self
.with_current_frame(|frame| frame.duration)?
.map(|frame_duration| {
frame_duration
.checked_sub(self.elapsed_since_frame_change)
.unwrap_or_default()
});
Ok(duration)
}
fn advance_frame(&mut self) -> crate::Result<()> {
self.current_frame = self.next_frame()?;
Ok(())
}
#[allow(clippy::cast_possible_wrap, clippy::cast_sign_loss)]
fn next_frame(&mut self) -> crate::Result<usize> {
let starting_frame = self.current_frame as i32;
let animation = self
.animations
.animations
.get(&self.current_tag)
.ok_or(Error::InvalidSpriteTag)?;
let next_frame = match self.current_animation_direction {
AnimationDirection::Forward => starting_frame + 1,
AnimationDirection::Reverse => starting_frame - 1,
};
Ok(if next_frame < 0 {
match animation.mode {
AnimationMode::Forward => unreachable!(),
AnimationMode::Reverse => {
animation.frames.len() - 1
}
AnimationMode::PingPong => {
self.current_animation_direction = AnimationDirection::Forward;
1
}
}
} else if next_frame as usize >= animation.frames.len() {
match animation.mode {
AnimationMode::Reverse => unreachable!(),
AnimationMode::Forward => 0,
AnimationMode::PingPong => {
self.current_animation_direction = AnimationDirection::Reverse;
(animation.frames.len() - 2).max(0)
}
}
} else {
next_frame as usize
})
}
fn with_current_frame<F, R>(&self, f: F) -> crate::Result<R>
where
F: Fn(&SpriteFrame) -> R,
{
let animation = self
.animations
.animations
.get(&self.current_tag)
.ok_or(Error::InvalidSpriteTag)?;
Ok(f(&animation.frames[self.current_frame]))
}
}
#[derive(Clone, Debug)]
pub struct SpriteAnimations {
animations: Arc<HashMap<Option<String>, SpriteAnimation>>,
}
impl SpriteAnimations {
#[must_use]
pub fn new(animations: HashMap<Option<String>, SpriteAnimation>) -> Self {
Self {
animations: Arc::new(animations),
}
}
#[must_use]
pub fn animation_for(&self, tag: &Option<impl ToString>) -> Option<&'_ SpriteAnimation> {
self.animations.get(&tag.as_ref().map(ToString::to_string))
}
}
#[derive(Debug, Clone)]
pub struct SpriteAnimation {
pub frames: Vec<SpriteFrame>,
pub mode: AnimationMode,
}
impl SpriteAnimation {
#[must_use]
pub fn new(frames: Vec<SpriteFrame>) -> Self {
Self {
frames,
mode: AnimationMode::Forward,
}
}
#[must_use]
pub const fn with_mode(mut self, mode: AnimationMode) -> Self {
self.mode = mode;
self
}
}
#[derive(Debug, Clone)]
pub struct SpriteFrame {
pub source: SpriteSource,
pub duration: Option<Duration>,
}
impl SpriteFrame {
#[must_use]
pub const fn new(source: SpriteSource) -> Self {
Self {
source,
duration: None,
}
}
#[must_use]
pub const fn with_duration(mut self, duration: Duration) -> Self {
self.duration = Some(duration);
self
}
}
#[derive(Clone, Debug)]
pub struct RenderedSprite {
pub(crate) data: Arc<RenderedSpriteData>,
}
impl RenderedSprite {
#[must_use]
pub(crate) fn new(
render_at: ExtentsRect<f32, Pixels>,
rotation: SpriteRotation<Pixels>,
alpha: f32,
source: SpriteSource,
) -> Self {
Self {
data: Arc::new(RenderedSpriteData {
render_at,
rotation,
alpha,
source,
}),
}
}
}
#[derive(Debug)]
pub(crate) struct RenderedSpriteData {
pub render_at: ExtentsRect<f32, Pixels>,
pub rotation: SpriteRotation<Pixels>,
pub alpha: f32,
pub source: SpriteSource,
}
#[derive(Copy, Clone, Debug)]
#[must_use]
pub struct SpriteRotation<Unit = Scaled> {
pub angle: Option<Angle>,
pub location: Option<Point<f32, Unit>>,
}
impl SpriteRotation<Pixels> {
pub const fn none() -> Self {
Self {
angle: None,
location: None,
}
}
pub const fn around_center(angle: Angle) -> Self {
Self {
angle: Some(angle),
location: None,
}
}
}
impl<Unit> Default for SpriteRotation<Unit> {
fn default() -> Self {
Self {
angle: None,
location: None,
}
}
}
impl<Unit> SpriteRotation<Unit> {
pub const fn around(angle: Angle, location: Point<f32, Unit>) -> Self {
Self {
angle: Some(angle),
location: Some(location),
}
}
}
impl Displayable<f32> for SpriteRotation<Pixels> {
type Pixels = Self;
type Points = SpriteRotation<Points>;
type Scaled = SpriteRotation<Scaled>;
fn to_pixels(&self, _scale: &figures::DisplayScale<f32>) -> Self::Pixels {
*self
}
fn to_points(&self, scale: &figures::DisplayScale<f32>) -> Self::Points {
SpriteRotation {
angle: self.angle,
location: self.location.map(|l| l.to_points(scale)),
}
}
fn to_scaled(&self, scale: &figures::DisplayScale<f32>) -> Self::Scaled {
SpriteRotation {
angle: self.angle,
location: self.location.map(|l| l.to_scaled(scale)),
}
}
}
impl Displayable<f32> for SpriteRotation<Points> {
type Pixels = SpriteRotation<Pixels>;
type Points = Self;
type Scaled = SpriteRotation<Scaled>;
fn to_pixels(&self, scale: &figures::DisplayScale<f32>) -> Self::Pixels {
SpriteRotation {
angle: self.angle,
location: self.location.map(|l| l.to_pixels(scale)),
}
}
fn to_points(&self, _scale: &figures::DisplayScale<f32>) -> Self::Points {
*self
}
fn to_scaled(&self, scale: &figures::DisplayScale<f32>) -> Self::Scaled {
SpriteRotation {
angle: self.angle,
location: self.location.map(|l| l.to_scaled(scale)),
}
}
}
impl Displayable<f32> for SpriteRotation<Scaled> {
type Pixels = SpriteRotation<Pixels>;
type Points = SpriteRotation<Points>;
type Scaled = Self;
fn to_pixels(&self, scale: &figures::DisplayScale<f32>) -> Self::Pixels {
SpriteRotation {
angle: self.angle,
location: self.location.map(|l| l.to_pixels(scale)),
}
}
fn to_points(&self, scale: &figures::DisplayScale<f32>) -> Self::Points {
SpriteRotation {
angle: self.angle,
location: self.location.map(|l| l.to_points(scale)),
}
}
fn to_scaled(&self, _scale: &figures::DisplayScale<f32>) -> Self::Scaled {
*self
}
}
pub struct Srgb;
pub struct Normal;