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#[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#[derive(Debug, Clone)]
44pub enum AnimationMode {
45 Forward,
47 Reverse,
49 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#[derive(Debug, Clone)]
76pub struct Sprite {
77 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 #[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 #[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 #[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 #[allow(clippy::too_many_lines)]
148 pub fn load_aseprite_json(raw_json: &str, texture: &Texture) -> crate::Result<Self> {
151 let json = json::parse(raw_json)?;
152
153 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 let name = name.split('.').next().unwrap();
172 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 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 #[must_use]
310 pub fn current_tag(&self) -> Option<&'_ str> {
311 self.current_tag.as_deref()
312 }
313
314 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 #[inline]
340 pub fn current_frame(&self) -> crate::Result<SpriteSource> {
341 self.with_current_frame(|frame| frame.source.clone())
342 }
343
344 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 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 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#[derive(Clone, Debug)]
422pub struct SpriteAnimations {
423 animations: Arc<HashMap<Option<String>, SpriteAnimation>>,
424}
425
426impl SpriteAnimations {
427 #[must_use]
429 pub fn new(animations: HashMap<Option<String>, SpriteAnimation>) -> Self {
430 Self {
431 animations: Arc::new(animations),
432 }
433 }
434
435 #[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#[derive(Debug, Clone)]
444pub struct SpriteAnimation {
445 pub frames: Vec<SpriteFrame>,
447 pub mode: AnimationMode,
449}
450
451impl SpriteAnimation {
452 #[must_use]
454 pub fn new(frames: Vec<SpriteFrame>) -> Self {
455 Self {
456 frames,
457 mode: AnimationMode::Forward,
458 }
459 }
460
461 #[must_use]
463 pub const fn with_mode(mut self, mode: AnimationMode) -> Self {
464 self.mode = mode;
465 self
466 }
467}
468
469#[derive(Debug, Clone)]
471pub struct SpriteFrame {
472 pub source: SpriteSource,
474 pub duration: Option<Duration>,
477}
478
479impl SpriteFrame {
480 #[must_use]
482 pub const fn new(source: SpriteSource) -> Self {
483 Self {
484 source,
485 duration: None,
486 }
487 }
488
489 #[must_use]
491 pub const fn with_duration(mut self, duration: Duration) -> Self {
492 self.duration = Some(duration);
493 self
494 }
495}
496
497#[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#[derive(Copy, Clone, Debug)]
532#[must_use]
533pub struct SpriteRotation<Unit = Scaled> {
534 pub angle: Option<Angle>,
536 pub location: Option<Point<f32, Unit>>,
539}
540
541impl SpriteRotation<Pixels> {
542 pub const fn none() -> Self {
544 Self {
545 angle: None,
546 location: None,
547 }
548 }
549
550 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 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
650pub struct Srgb;
653pub struct Normal;