kludgine_core/
sprite.rs

1use figures::{Displayable, Pixels, Points, Scaled};
2
3use crate::math::{Angle, ExtentsRect, Point, Rect, Size};
4use crate::texture::Texture;
5use crate::Error;
6mod batch;
7mod collection;
8mod gpu_batch;
9mod pipeline;
10mod sheet;
11pub(crate) use self::batch::Batch;
12pub(crate) use self::gpu_batch::{BatchBuffers, GpuBatch};
13pub(crate) use self::pipeline::Pipeline;
14
15mod source;
16use std::collections::HashMap;
17use std::iter::IntoIterator;
18use std::sync::Arc;
19use std::time::Duration;
20
21pub use self::collection::*;
22pub use self::pipeline::VertexShaderSource;
23pub use self::sheet::*;
24pub use self::source::*;
25
26/// Includes an [Aseprite](https://www.aseprite.org/) sprite sheet and Json
27/// export. For more information, see [`Sprite::load_aseprite_json`]. This macro
28/// will append ".png" and ".json" to the path provided and include both files
29/// in your binary.
30#[macro_export]
31macro_rules! include_aseprite_sprite {
32    ($path:expr) => {{
33        $crate::include_texture!(concat!($path, ".png")).and_then(|texture| {
34            $crate::sprite::Sprite::load_aseprite_json(
35                include_str!(concat!($path, ".json")),
36                &texture,
37            )
38        })
39    }};
40}
41
42/// The animation mode of the sprite.
43#[derive(Debug, Clone)]
44pub enum AnimationMode {
45    /// Iterate frames in order. When at the end, reset to the start.
46    Forward,
47    /// Iterate frames in reverse order. When at the start, reset to the end.
48    Reverse,
49    /// Iterate frames starting at the beginning and continuously iterating
50    /// forwards and backwards across the frames, changing direction whenever
51    /// the start or end are reached.
52    PingPong,
53}
54
55impl AnimationMode {
56    const fn default_direction(&self) -> AnimationDirection {
57        match self {
58            AnimationMode::Forward | AnimationMode::PingPong => AnimationDirection::Forward,
59            AnimationMode::Reverse => AnimationDirection::Reverse,
60        }
61    }
62}
63
64#[derive(Debug, Clone)]
65enum AnimationDirection {
66    Forward,
67    Reverse,
68}
69
70/// A sprite is a renderable graphic with optional animations.
71///
72/// Cloning a sprite is cheap. When cloning, the animations will be shared
73/// between all clones of the sprite, but each sprite will track its current
74/// frame/tag independently.
75#[derive(Debug, Clone)]
76pub struct Sprite {
77    /// The animations that form this sprite.
78    pub animations: SpriteAnimations,
79    elapsed_since_frame_change: Duration,
80    current_tag: Option<String>,
81    current_frame: usize,
82    current_animation_direction: AnimationDirection,
83}
84
85impl From<SpriteAnimations> for Sprite {
86    fn from(animations: SpriteAnimations) -> Self {
87        Self::new(animations)
88    }
89}
90
91impl Sprite {
92    /// Returns a new sprite with `animations`.
93    #[must_use]
94    pub const fn new(animations: SpriteAnimations) -> Self {
95        Self {
96            animations,
97            current_frame: 0,
98            current_tag: None,
99            elapsed_since_frame_change: Duration::from_millis(0),
100            current_animation_direction: AnimationDirection::Forward,
101        }
102    }
103
104    /// For merging multiple Sprites that have no tags within them
105    #[must_use]
106    pub fn merged<S: Into<String>, I: IntoIterator<Item = (S, Self)>>(source: I) -> Self {
107        let mut combined = HashMap::new();
108        for (name, sprite) in source {
109            combined.insert(
110                Some(name.into()),
111                sprite
112                    .animations
113                    .animation_for(&Option::<&str>::None)
114                    .unwrap()
115                    .clone(),
116            );
117        }
118        Self::new(SpriteAnimations::new(combined))
119    }
120
121    /// Creates an instance from a texture. This creates a `SpriteAnimation`
122    /// with no tag and a single frame.
123    #[must_use]
124    pub fn single_frame(texture: Texture) -> Self {
125        let source = SpriteSource::entire_texture(texture);
126        let mut frames = HashMap::new();
127        frames.insert(
128            None,
129            SpriteAnimation::new(vec![SpriteFrame {
130                source,
131                duration: None,
132            }])
133            .with_mode(AnimationMode::Forward),
134        );
135        let frames = SpriteAnimations::new(frames);
136
137        Self::new(frames)
138    }
139
140    /// Loads [Aseprite](https://www.aseprite.org/) JSON export format, when
141    /// using the correct settings.
142    ///
143    /// For the JSON data, use the Hash export option (default), and use either
144    /// spaces or underscores (_) inbetween the fields in the name. Ensure
145    /// `{frame}` is the last field in the name before the extension. E.g.,
146    /// `{tag}_{frame}.{extension}`
147    #[allow(clippy::too_many_lines)]
148    // TODO refactor. Now that I know more about serde, this probably can be parsed
149    // with a complex serde type.
150    pub fn load_aseprite_json(raw_json: &str, texture: &Texture) -> crate::Result<Self> {
151        let json = json::parse(raw_json)?;
152
153        // Validate the data
154        let meta = &json["meta"];
155        if !meta.is_object() {
156            return Err(Error::SpriteParse(
157                "invalid aseprite json: No `meta` section".to_owned(),
158            ));
159        }
160
161        let texture_size = texture.size();
162        if meta["size"]["w"] != texture_size.width || meta["size"]["h"] != texture_size.height {
163            return Err(Error::SpriteParse(
164                "invalid aseprite json: Size did not match input texture".to_owned(),
165            ));
166        }
167
168        let mut frames = HashMap::new();
169        for (name, frame) in json["frames"].entries() {
170            // Remove the extension, if present
171            let name = name.split('.').next().unwrap();
172            // Split by _ or ' 'as per the documentation of this method.
173            let name_parts = name.split(|c| c == '_' || c == ' ').collect::<Vec<_>>();
174            let frame_number = name_parts[name_parts.len() - 1]
175                .parse::<usize>()
176                .or_else(|_| {
177                    if json["frames"].len() == 1 {
178                        Ok(0)
179                    } else {
180                        Err(Error::SpriteParse(
181                            "invalid aseprite json: frame was not numeric.".to_owned(),
182                        ))
183                    }
184                })?;
185
186            let duration = match frame["duration"].as_u64() {
187                Some(millis) => Duration::from_millis(millis),
188                None => {
189                    return Err(Error::SpriteParse(
190                        "invalid aseprite json: invalid duration".to_owned(),
191                    ))
192                }
193            };
194
195            let frame = Rect::new(
196                Point::new(
197                    frame["frame"]["x"].as_u32().ok_or_else(|| {
198                        Error::SpriteParse(
199                            "invalid aseprite json: frame x was not valid".to_owned(),
200                        )
201                    })?,
202                    frame["frame"]["y"].as_u32().ok_or_else(|| {
203                        Error::SpriteParse(
204                            "invalid aseprite json: frame y was not valid".to_owned(),
205                        )
206                    })?,
207                ),
208                Size::new(
209                    frame["frame"]["w"].as_u32().ok_or_else(|| {
210                        Error::SpriteParse(
211                            "invalid aseprite json: frame w was not valid".to_owned(),
212                        )
213                    })?,
214                    frame["frame"]["h"].as_u32().ok_or_else(|| {
215                        Error::SpriteParse(
216                            "invalid aseprite json: frame h was not valid".to_owned(),
217                        )
218                    })?,
219                ),
220            );
221
222            let source = SpriteSource::new(frame, texture.clone());
223
224            frames.insert(
225                frame_number,
226                SpriteFrame {
227                    duration: Some(duration),
228                    source,
229                },
230            );
231        }
232
233        let mut animations = HashMap::new();
234        for tag in meta["frameTags"].members() {
235            let direction = if tag["direction"] == "forward" {
236                AnimationMode::Forward
237            } else if tag["direction"] == "reverse" {
238                AnimationMode::Reverse
239            } else if tag["direction"] == "pingpong" {
240                AnimationMode::PingPong
241            } else {
242                return Err(Error::SpriteParse(
243                    "invalid aseprite json: frameTags direction is an unknown value".to_owned(),
244                ));
245            };
246
247            let name = tag["name"].as_str().map(str::to_owned);
248
249            let start_frame = tag["from"].as_usize().ok_or_else(|| {
250                Error::SpriteParse(
251                    "invalid aseprite json: frameTags from was not numeric".to_owned(),
252                )
253            })?;
254            let end_frame = tag["to"].as_usize().ok_or_else(|| {
255                Error::SpriteParse(
256                    "invalid aseprite json: frameTags from was not numeric".to_owned(),
257                )
258            })?;
259            let mut animation_frames = Vec::new();
260            for i in start_frame..=end_frame {
261                let frame = frames.get(&i).ok_or_else(|| {
262                    Error::SpriteParse(
263                        "invalid aseprite json: frameTags frame was out of bounds".to_owned(),
264                    )
265                })?;
266                animation_frames.push(frame.clone());
267            }
268
269            animations.insert(
270                name,
271                SpriteAnimation::new(animation_frames).with_mode(direction),
272            );
273        }
274
275        let mut frames: Vec<_> = frames.into_iter().collect();
276        frames.sort_by(|a, b| a.0.cmp(&b.0));
277
278        animations.insert(
279            None,
280            SpriteAnimation::new(frames.iter().map(|(_, f)| f.clone()).collect())
281                .with_mode(AnimationMode::Forward),
282        );
283
284        Ok(Self::new(SpriteAnimations::new(animations)))
285    }
286
287    /// Sets the current tag for the animation. If the tag currently matches,
288    /// nothing will happen. If it is a new tag, the current frame and animation
289    /// direction will be switched to the values from the new tag.
290    pub fn set_current_tag<S: Into<String>>(&mut self, tag: Option<S>) -> crate::Result<()> {
291        let new_tag = tag.map(Into::into);
292        if self.current_tag != new_tag {
293            self.current_animation_direction = {
294                let animation = self
295                    .animations
296                    .animations
297                    .get(&new_tag)
298                    .ok_or(Error::InvalidSpriteTag)?;
299                animation.mode.default_direction()
300            };
301            self.current_frame = 0;
302            self.current_tag = new_tag;
303        }
304
305        Ok(())
306    }
307
308    /// Returns the current tag.
309    #[must_use]
310    pub fn current_tag(&self) -> Option<&'_ str> {
311        self.current_tag.as_deref()
312    }
313
314    /// Gets the current frame after advancing the animation for `elapsed`
315    /// duration. If you need to invoke this multiple times in a single frame,
316    /// pass `None` on subsequent calls. In general, you should clone sprites
317    /// rather than reuse them. Kludgine ensures that your texture and animation
318    /// data will be shared and not cloned.
319    pub fn get_frame(&mut self, elapsed: Option<Duration>) -> crate::Result<SpriteSource> {
320        if let Some(elapsed) = elapsed {
321            self.elapsed_since_frame_change += elapsed;
322
323            let current_frame_duration = self.with_current_frame(|frame| frame.duration)?;
324            if let Some(frame_duration) = current_frame_duration {
325                if self.elapsed_since_frame_change > frame_duration {
326                    self.elapsed_since_frame_change = Duration::from_nanos(
327                        (self.elapsed_since_frame_change.as_nanos() % frame_duration.as_nanos())
328                            as u64,
329                    );
330                    self.advance_frame()?;
331                }
332            }
333        }
334
335        self.current_frame()
336    }
337
338    /// Retrieve the current animation frame, if set and valid.
339    #[inline]
340    pub fn current_frame(&self) -> crate::Result<SpriteSource> {
341        self.with_current_frame(|frame| frame.source.clone())
342    }
343
344    /// Returns the amount of time remaining until the next frame is due to be
345    /// shown for this sprite. Can be used to calculate redraws more efficiently
346    /// if you're not rendering at a constant framerate.
347    pub fn remaining_frame_duration(&self) -> crate::Result<Option<Duration>> {
348        let duration = self
349            .with_current_frame(|frame| frame.duration)?
350            .map(|frame_duration| {
351                frame_duration
352                    .checked_sub(self.elapsed_since_frame_change)
353                    .unwrap_or_default()
354            });
355
356        Ok(duration)
357    }
358
359    fn advance_frame(&mut self) -> crate::Result<()> {
360        self.current_frame = self.next_frame()?;
361        Ok(())
362    }
363
364    #[allow(clippy::cast_possible_wrap, clippy::cast_sign_loss)]
365    fn next_frame(&mut self) -> crate::Result<usize> {
366        let starting_frame = self.current_frame as i32;
367        let animation = self
368            .animations
369            .animations
370            .get(&self.current_tag)
371            .ok_or(Error::InvalidSpriteTag)?;
372
373        let next_frame = match self.current_animation_direction {
374            AnimationDirection::Forward => starting_frame + 1,
375            AnimationDirection::Reverse => starting_frame - 1,
376        };
377
378        Ok(if next_frame < 0 {
379            match animation.mode {
380                AnimationMode::Forward => unreachable!(),
381                AnimationMode::Reverse => {
382                    // Cycle back to the last frame
383                    animation.frames.len() - 1
384                }
385                AnimationMode::PingPong => {
386                    self.current_animation_direction = AnimationDirection::Forward;
387                    1
388                }
389            }
390        } else if next_frame as usize >= animation.frames.len() {
391            match animation.mode {
392                AnimationMode::Reverse => unreachable!(),
393                AnimationMode::Forward => 0,
394                AnimationMode::PingPong => {
395                    self.current_animation_direction = AnimationDirection::Reverse;
396                    (animation.frames.len() - 2).max(0)
397                }
398            }
399        } else {
400            next_frame as usize
401        })
402    }
403
404    /// If tag is valid, invoke `f` with the current animation frame.
405    fn with_current_frame<F, R>(&self, f: F) -> crate::Result<R>
406    where
407        F: Fn(&SpriteFrame) -> R,
408    {
409        let animation = self
410            .animations
411            .animations
412            .get(&self.current_tag)
413            .ok_or(Error::InvalidSpriteTag)?;
414
415        Ok(f(&animation.frames[self.current_frame]))
416    }
417}
418
419/// A collection of [`SpriteAnimation`]s. This is an immutable object that
420/// shares data when cloned to minimize data copies.
421#[derive(Clone, Debug)]
422pub struct SpriteAnimations {
423    animations: Arc<HashMap<Option<String>, SpriteAnimation>>,
424}
425
426impl SpriteAnimations {
427    /// Creates a new collection from `animations`.
428    #[must_use]
429    pub fn new(animations: HashMap<Option<String>, SpriteAnimation>) -> Self {
430        Self {
431            animations: Arc::new(animations),
432        }
433    }
434
435    /// Returns the animation for `tag`.
436    #[must_use]
437    pub fn animation_for(&self, tag: &Option<impl ToString>) -> Option<&'_ SpriteAnimation> {
438        self.animations.get(&tag.as_ref().map(ToString::to_string))
439    }
440}
441
442/// An animation of one or more [`SpriteFrame`]s.
443#[derive(Debug, Clone)]
444pub struct SpriteAnimation {
445    /// The frames of the animation.
446    pub frames: Vec<SpriteFrame>,
447    /// The mode of the animation.
448    pub mode: AnimationMode,
449}
450
451impl SpriteAnimation {
452    /// Creates a new animation with `frames` and [`AnimationMode::Forward`].
453    #[must_use]
454    pub fn new(frames: Vec<SpriteFrame>) -> Self {
455        Self {
456            frames,
457            mode: AnimationMode::Forward,
458        }
459    }
460
461    /// Builder-style function. Sets `mode` and returns self.
462    #[must_use]
463    pub const fn with_mode(mut self, mode: AnimationMode) -> Self {
464        self.mode = mode;
465        self
466    }
467}
468
469/// A single frame for a [`SpriteAnimation`].
470#[derive(Debug, Clone)]
471pub struct SpriteFrame {
472    /// The source to render.
473    pub source: SpriteSource,
474    /// The length the frame should be displayed. `None` will act as an infinite
475    /// duration.
476    pub duration: Option<Duration>,
477}
478
479impl SpriteFrame {
480    /// Creates a new frame with `source` and no duration.
481    #[must_use]
482    pub const fn new(source: SpriteSource) -> Self {
483        Self {
484            source,
485            duration: None,
486        }
487    }
488
489    /// Builder-style function. Sets `duration` and returns self.
490    #[must_use]
491    pub const fn with_duration(mut self, duration: Duration) -> Self {
492        self.duration = Some(duration);
493        self
494    }
495}
496
497/// A rendered sprite.
498#[derive(Clone, Debug)]
499pub struct RenderedSprite {
500    pub(crate) data: Arc<RenderedSpriteData>,
501}
502
503impl RenderedSprite {
504    #[must_use]
505    pub(crate) fn new(
506        render_at: ExtentsRect<f32, Pixels>,
507        rotation: SpriteRotation<Pixels>,
508        alpha: f32,
509        source: SpriteSource,
510    ) -> Self {
511        Self {
512            data: Arc::new(RenderedSpriteData {
513                render_at,
514                rotation,
515                alpha,
516                source,
517            }),
518        }
519    }
520}
521
522#[derive(Debug)]
523pub(crate) struct RenderedSpriteData {
524    pub render_at: ExtentsRect<f32, Pixels>,
525    pub rotation: SpriteRotation<Pixels>,
526    pub alpha: f32,
527    pub source: SpriteSource,
528}
529
530/// A rotation of a sprite.
531#[derive(Copy, Clone, Debug)]
532#[must_use]
533pub struct SpriteRotation<Unit = Scaled> {
534    /// The angle to rotate around `screen_location`.
535    pub angle: Option<Angle>,
536    /// The location to rotate the sprite around. If not specified, the center
537    /// of the sprite is used.
538    pub location: Option<Point<f32, Unit>>,
539}
540
541impl SpriteRotation<Pixels> {
542    /// Returns a value that performs no rotation.
543    pub const fn none() -> Self {
544        Self {
545            angle: None,
546            location: None,
547        }
548    }
549
550    /// Returns a rotation around the center of the shape.
551    pub const fn around_center(angle: Angle) -> Self {
552        Self {
553            angle: Some(angle),
554            location: None,
555        }
556    }
557}
558
559impl<Unit> Default for SpriteRotation<Unit> {
560    fn default() -> Self {
561        Self {
562            angle: None,
563            location: None,
564        }
565    }
566}
567
568impl<Unit> SpriteRotation<Unit> {
569    /// Returns a rotation around `location`.
570    pub const fn around(angle: Angle, location: Point<f32, Unit>) -> Self {
571        Self {
572            angle: Some(angle),
573            location: Some(location),
574        }
575    }
576}
577
578impl Displayable<f32> for SpriteRotation<Pixels> {
579    type Pixels = Self;
580    type Points = SpriteRotation<Points>;
581    type Scaled = SpriteRotation<Scaled>;
582
583    fn to_pixels(&self, _scale: &figures::DisplayScale<f32>) -> Self::Pixels {
584        *self
585    }
586
587    fn to_points(&self, scale: &figures::DisplayScale<f32>) -> Self::Points {
588        SpriteRotation {
589            angle: self.angle,
590            location: self.location.map(|l| l.to_points(scale)),
591        }
592    }
593
594    fn to_scaled(&self, scale: &figures::DisplayScale<f32>) -> Self::Scaled {
595        SpriteRotation {
596            angle: self.angle,
597            location: self.location.map(|l| l.to_scaled(scale)),
598        }
599    }
600}
601
602impl Displayable<f32> for SpriteRotation<Points> {
603    type Pixels = SpriteRotation<Pixels>;
604    type Points = Self;
605    type Scaled = SpriteRotation<Scaled>;
606
607    fn to_pixels(&self, scale: &figures::DisplayScale<f32>) -> Self::Pixels {
608        SpriteRotation {
609            angle: self.angle,
610            location: self.location.map(|l| l.to_pixels(scale)),
611        }
612    }
613
614    fn to_points(&self, _scale: &figures::DisplayScale<f32>) -> Self::Points {
615        *self
616    }
617
618    fn to_scaled(&self, scale: &figures::DisplayScale<f32>) -> Self::Scaled {
619        SpriteRotation {
620            angle: self.angle,
621            location: self.location.map(|l| l.to_scaled(scale)),
622        }
623    }
624}
625
626impl Displayable<f32> for SpriteRotation<Scaled> {
627    type Pixels = SpriteRotation<Pixels>;
628    type Points = SpriteRotation<Points>;
629    type Scaled = Self;
630
631    fn to_pixels(&self, scale: &figures::DisplayScale<f32>) -> Self::Pixels {
632        SpriteRotation {
633            angle: self.angle,
634            location: self.location.map(|l| l.to_pixels(scale)),
635        }
636    }
637
638    fn to_points(&self, scale: &figures::DisplayScale<f32>) -> Self::Points {
639        SpriteRotation {
640            angle: self.angle,
641            location: self.location.map(|l| l.to_points(scale)),
642        }
643    }
644
645    fn to_scaled(&self, _scale: &figures::DisplayScale<f32>) -> Self::Scaled {
646        *self
647    }
648}
649
650/// The Srgb colorspace. Used as a `VertexShaderSource` in
651/// [`FrameRenderer`](crate::frame_renderer::FrameRenderer).
652pub struct Srgb;
653/// The uncorrected Rgb colorspace. Used as a
654/// [`VertexShaderSource`](crate::sprite::VertexShaderSource) in
655/// [`FrameRenderer`](crate::frame_renderer::FrameRenderer).
656pub struct Normal;