1#![warn(missing_docs)]
25
26use crate::{
27 core::{
28 algebra::Vector2,
29 math::Rect,
30 reflect::prelude::*,
31 uuid::{uuid, Uuid},
32 uuid_provider,
33 visitor::prelude::*,
34 TypeUuidProvider,
35 },
36 spritesheet::signal::Signal,
37};
38use std::collections::vec_deque::VecDeque;
39use strum_macros::{AsRefStr, EnumString, VariantNames};
40
41pub mod signal;
42
43pub trait SpriteSheetTexture: PartialEq + Clone + Visit + Reflect + 'static {}
45
46impl<T: PartialEq + Clone + Visit + Reflect + 'static> SpriteSheetTexture for T {}
47
48#[derive(Visit, Reflect, Copy, Clone, Eq, PartialEq, Debug, AsRefStr, EnumString, VariantNames)]
50pub enum Status {
51 Playing,
53
54 Stopped,
57
58 Paused,
60}
61
62uuid_provider!(Status = "74a31122-a7a8-476c-ab87-77e53cf0523c");
63
64impl Default for Status {
65 fn default() -> Self {
66 Self::Stopped
67 }
68}
69
70#[derive(Visit, Reflect, Clone, Debug, Eq, PartialEq)]
72#[non_exhaustive]
73pub enum Event {
74 Signal(u64),
76}
77
78impl Default for Event {
79 fn default() -> Self {
80 Self::Signal(0)
81 }
82}
83
84#[derive(Reflect, Visit, Clone, Debug, PartialEq, Eq)]
86pub struct SpriteSheetFramesContainer<T>
87where
88 T: SpriteSheetTexture,
89{
90 size: Vector2<u32>,
91 frames: Vec<Vector2<u32>>,
92 #[visit(optional)]
93 texture: Option<T>,
94}
95
96impl<T> SpriteSheetFramesContainer<T>
97where
98 T: SpriteSheetTexture,
99{
100 pub fn push(&mut self, bounds: Vector2<u32>) {
102 self.frames.push(bounds)
103 }
104
105 pub fn remove(&mut self, index: usize) -> Vector2<u32> {
107 self.frames.remove(index)
108 }
109
110 pub fn len(&self) -> usize {
112 self.frames.len()
113 }
114
115 pub fn is_empty(&self) -> bool {
117 self.frames.is_empty()
118 }
119
120 pub fn get(&self, index: usize) -> Option<&Vector2<u32>> {
122 self.frames.get(index)
123 }
124
125 pub fn set_size(&mut self, size: Vector2<u32>) {
127 self.size = Vector2::new(size.x.max(1), size.y.max(1));
128 }
129
130 pub fn size(&self) -> Vector2<u32> {
132 self.size
133 }
134
135 pub fn sort_by_position(&mut self) {
139 self.frames.sort_by_key(|p| p.y * self.size.x + p.x)
140 }
141
142 pub fn iter(&self) -> impl Iterator<Item = &Vector2<u32>> {
144 self.frames.iter()
145 }
146
147 pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut Vector2<u32>> {
149 self.frames.iter_mut()
150 }
151
152 pub fn texture(&self) -> Option<T> {
154 self.texture.clone()
155 }
156}
157
158impl<T> Default for SpriteSheetFramesContainer<T>
159where
160 T: SpriteSheetTexture,
161{
162 fn default() -> Self {
163 Self {
164 size: Vector2::new(1, 1),
165 frames: vec![],
166 texture: None,
167 }
168 }
169}
170
171#[derive(Visit, Reflect, Clone, Debug)]
174pub struct SpriteSheetAnimation<T>
175where
176 T: SpriteSheetTexture,
177{
178 #[visit(rename = "Frames")]
179 frames_container: SpriteSheetFramesContainer<T>,
180 current_frame: f32,
181 speed: f32,
182 status: Status,
183 looping: bool,
184 signals: Vec<Signal>,
185 #[visit(optional)]
186 #[reflect(setter = "set_texture")]
187 texture: Option<T>,
188 #[reflect(hidden)]
189 #[visit(skip)]
190 events: VecDeque<Event>,
191 #[visit(optional)]
192 max_event_capacity: usize,
193}
194
195impl<T: SpriteSheetTexture> PartialEq for SpriteSheetAnimation<T> {
196 fn eq(&self, other: &Self) -> bool {
197 self.frames_container == other.frames_container
198 && self.current_frame == other.current_frame
199 && self.speed == other.speed
200 && self.looping == other.looping
201 && self.signals == other.signals
202 && self.texture == other.texture
203 }
204}
205
206impl<T> TypeUuidProvider for SpriteSheetAnimation<T>
207where
208 T: SpriteSheetTexture,
209{
210 fn type_uuid() -> Uuid {
211 uuid!("1fa13feb-a16d-4539-acde-672aaeb0f62b")
212 }
213}
214
215impl<T> Default for SpriteSheetAnimation<T>
216where
217 T: SpriteSheetTexture,
218{
219 fn default() -> Self {
220 Self {
221 frames_container: Default::default(),
222 current_frame: 0.0,
223 speed: 10.0,
224 status: Default::default(),
225 looping: true,
226 signals: Default::default(),
227 texture: None,
228 events: Default::default(),
229 max_event_capacity: 32,
230 }
231 }
232}
233
234#[derive(Clone, Debug, PartialEq, Eq)]
237pub struct ImageParameters {
238 pub width: u32,
240
241 pub height: u32,
243
244 pub frame_width: u32,
246
247 pub frame_height: u32,
249
250 pub first_frame: u32,
252
253 pub last_frame: u32,
255
256 pub column_major: bool,
258}
259
260impl<T> SpriteSheetAnimation<T>
261where
262 T: SpriteSheetTexture,
263{
264 pub fn new() -> Self {
266 Self::default()
267 }
268
269 pub fn new_from_image_parameters(params: ImageParameters) -> Self {
337 let ImageParameters {
338 width,
339 height,
340 frame_width,
341 frame_height,
342 first_frame,
343 last_frame,
344 column_major,
345 } = params;
346
347 let width_in_frames = width / frame_width;
348 let height_in_frames = height / frame_height;
349
350 let frames = (first_frame..last_frame)
351 .map(|n| {
352 let x = if column_major {
353 n / width_in_frames
354 } else {
355 n % width_in_frames
356 };
357 let y = if column_major {
358 n % height_in_frames
359 } else {
360 n / height_in_frames
361 };
362
363 Vector2::new(x, y)
364 })
365 .collect::<Vec<_>>();
366
367 Self {
368 frames_container: SpriteSheetFramesContainer {
369 frames,
370 size: Vector2::new(width_in_frames, height_in_frames),
371 texture: None,
372 },
373 ..Default::default()
374 }
375 }
376
377 pub fn with_container(container: SpriteSheetFramesContainer<T>) -> Self {
379 Self {
380 frames_container: container,
381 ..Default::default()
382 }
383 }
384
385 pub fn set_texture(&mut self, texture: Option<T>) -> Option<T> {
387 self.frames_container.texture.clone_from(&texture);
388 std::mem::replace(&mut self.texture, texture)
389 }
390
391 pub fn texture(&self) -> Option<T> {
393 self.texture.clone()
394 }
395
396 pub fn get_max_event_capacity(&self) -> usize {
398 self.max_event_capacity
399 }
400
401 pub fn set_max_event_capacity(&mut self, max_event_capacity: usize) {
403 self.max_event_capacity = max_event_capacity;
404 }
405
406 pub fn frames(&self) -> &SpriteSheetFramesContainer<T> {
408 &self.frames_container
409 }
410
411 pub fn frames_mut(&mut self) -> &mut SpriteSheetFramesContainer<T> {
413 &mut self.frames_container
414 }
415
416 pub fn add_frame(&mut self, frame: Vector2<u32>) {
418 self.frames_container.push(frame);
419 }
420
421 pub fn remove_frame(&mut self, index: usize) -> Option<Vector2<u32>> {
423 if index < self.frames_container.len() {
424 self.current_frame = self.current_frame.min(self.frames_container.len() as f32);
425 Some(self.frames_container.remove(index))
426 } else {
427 None
428 }
429 }
430
431 pub fn update(&mut self, dt: f32) {
433 if self.status != Status::Playing {
434 return;
435 }
436
437 if self.frames_container.is_empty() {
438 self.status = Status::Stopped;
439 return;
440 }
441
442 let next_frame = self.current_frame + self.speed * dt;
443
444 for signal in self.signals.iter_mut().filter(|s| s.enabled) {
445 let signal_frame = signal.frame as f32;
446
447 if (self.speed >= 0.0
448 && (self.current_frame < signal_frame && next_frame >= signal_frame)
449 || self.speed < 0.0
450 && (self.current_frame > signal_frame && next_frame <= signal_frame))
451 && self.events.len() < self.max_event_capacity
452 {
453 self.events.push_back(Event::Signal(signal.id));
454 }
455 }
456
457 self.current_frame = next_frame;
458 if self.current_frame >= self.frames_container.len() as f32 {
459 if self.looping {
460 self.current_frame = 0.0;
462 } else {
463 self.current_frame = self.frames_container.len().saturating_sub(1) as f32;
465 self.status = Status::Stopped;
466 }
467 } else if self.current_frame <= 0.0 {
468 if self.looping {
469 self.current_frame = self.frames_container.len().saturating_sub(1) as f32;
471 } else {
472 self.current_frame = 0.0;
474 self.status = Status::Stopped;
475 }
476 }
477 }
478
479 pub fn current_frame(&self) -> usize {
481 self.current_frame as usize
482 }
483
484 pub fn frame_uv_rect(&self, i: usize) -> Option<Rect<f32>> {
486 assert_ne!(self.frames_container.size.x, 0);
487 assert_ne!(self.frames_container.size.y, 0);
488
489 self.frames_container.get(i).map(|pos| Rect {
490 position: Vector2::new(
491 pos.x as f32 / self.frames_container.size.x as f32,
492 pos.y as f32 / self.frames_container.size.y as f32,
493 ),
494 size: Vector2::new(
495 1.0 / self.frames_container.size.x as f32,
496 1.0 / self.frames_container.size.y as f32,
497 ),
498 })
499 }
500
501 pub fn current_frame_uv_rect(&self) -> Option<Rect<f32>> {
503 self.frame_uv_rect(self.current_frame())
504 }
505
506 pub fn set_current_frame(&mut self, current_frame: usize) {
508 self.current_frame = current_frame.min(self.frames_container.len()) as f32;
509 }
510
511 pub fn is_looping(&self) -> bool {
513 self.looping
514 }
515
516 pub fn set_looping(&mut self, looping: bool) {
518 self.looping = looping;
519 }
520
521 pub fn speed(&self) -> f32 {
523 self.speed
524 }
525
526 pub fn set_speed(&mut self, speed: f32) {
529 self.speed = speed;
530 }
531
532 pub fn rewind_to_beginning(&mut self) {
534 self.current_frame = 0.0;
535 }
536
537 pub fn rewind_to_end(&mut self) {
539 self.current_frame = self.frames_container.len().saturating_sub(1) as f32;
540 }
541
542 pub fn status(&self) -> Status {
544 self.status
545 }
546
547 pub fn play(&mut self) {
549 self.status = Status::Playing;
550 }
551
552 pub fn is_playing(&self) -> bool {
554 self.status == Status::Playing
555 }
556
557 pub fn stop(&mut self) {
559 self.status = Status::Stopped;
560 self.rewind_to_beginning();
561 }
562
563 pub fn is_stopped(&self) -> bool {
565 self.status == Status::Stopped
566 }
567
568 pub fn pause(&mut self) {
570 self.status = Status::Paused;
571 }
572
573 pub fn is_paused(&self) -> bool {
575 self.status == Status::Paused
576 }
577
578 pub fn add_signal(&mut self, signal: Signal) {
580 self.signals.push(signal)
581 }
582
583 pub fn remove_signal(&mut self, id: u64) {
585 self.signals.retain(|s| s.id != id)
586 }
587
588 pub fn pop_event(&mut self) -> Option<Event> {
590 self.events.pop_front()
591 }
592}
593
594#[cfg(test)]
595mod test {
596 use crate::spritesheet::{
597 signal::Signal, Event, ImageParameters, SpriteSheetAnimation, Status,
598 };
599 use fyrox_core::{algebra::Vector2, math::Rect, reflect::prelude::*, visitor::prelude::*};
600
601 #[derive(PartialEq, Clone, Reflect, Visit, Debug)]
602 struct MyTexture {}
603
604 #[test]
605 fn test_sprite_sheet_one_row() {
606 let animation =
607 SpriteSheetAnimation::<MyTexture>::new_from_image_parameters(ImageParameters {
608 width: 128,
609 height: 128,
610 frame_width: 32,
611 frame_height: 32,
612 first_frame: 0,
613 last_frame: 4,
614 column_major: false,
615 });
616 assert_eq!(
617 animation.frame_uv_rect(0),
618 Some(Rect::new(0.0, 0.0, 0.25, 0.25))
619 );
620 assert_eq!(
621 animation.frame_uv_rect(1),
622 Some(Rect::new(0.25, 0.0, 0.25, 0.25))
623 );
624 assert_eq!(
625 animation.frame_uv_rect(2),
626 Some(Rect::new(0.5, 0.0, 0.25, 0.25))
627 );
628 assert_eq!(
629 animation.frame_uv_rect(3),
630 Some(Rect::new(0.75, 0.0, 0.25, 0.25))
631 );
632 }
633
634 #[test]
635 fn test_sprite_sheet_one_column() {
636 let animation =
637 SpriteSheetAnimation::<MyTexture>::new_from_image_parameters(ImageParameters {
638 width: 128,
639 height: 128,
640 frame_width: 32,
641 frame_height: 32,
642 first_frame: 0,
643 last_frame: 4,
644 column_major: true,
645 });
646 assert_eq!(
647 animation.frame_uv_rect(0),
648 Some(Rect::new(0.0, 0.0, 0.25, 0.25))
649 );
650 assert_eq!(
651 animation.frame_uv_rect(1),
652 Some(Rect::new(0.0, 0.25, 0.25, 0.25))
653 );
654 assert_eq!(
655 animation.frame_uv_rect(2),
656 Some(Rect::new(0.0, 0.5, 0.25, 0.25))
657 );
658 assert_eq!(
659 animation.frame_uv_rect(3),
660 Some(Rect::new(0.0, 0.75, 0.25, 0.25))
661 );
662 }
663
664 #[test]
665 fn test_sprite_sheet_row_partial() {
666 let animation =
667 SpriteSheetAnimation::<MyTexture>::new_from_image_parameters(ImageParameters {
668 width: 128,
669 height: 128,
670 frame_width: 32,
671 frame_height: 32,
672 first_frame: 2,
673 last_frame: 6,
674 column_major: false,
675 });
676 assert_eq!(
677 animation.frame_uv_rect(0),
678 Some(Rect::new(0.5, 0.0, 0.25, 0.25))
679 );
680 assert_eq!(
681 animation.frame_uv_rect(1),
682 Some(Rect::new(0.75, 0.0, 0.25, 0.25))
683 );
684 assert_eq!(
685 animation.frame_uv_rect(2),
686 Some(Rect::new(0.0, 0.25, 0.25, 0.25))
687 );
688 assert_eq!(
689 animation.frame_uv_rect(3),
690 Some(Rect::new(0.25, 0.25, 0.25, 0.25))
691 );
692 }
693
694 #[test]
695 fn test_sprite_sheet_column_partial() {
696 let animation =
697 SpriteSheetAnimation::<MyTexture>::new_from_image_parameters(ImageParameters {
698 width: 128,
699 height: 128,
700 frame_width: 32,
701 frame_height: 32,
702 first_frame: 2,
703 last_frame: 6,
704 column_major: true,
705 });
706 assert_eq!(
707 animation.frame_uv_rect(0),
708 Some(Rect::new(0.0, 0.5, 0.25, 0.25))
709 );
710 assert_eq!(
711 animation.frame_uv_rect(1),
712 Some(Rect::new(0.0, 0.75, 0.25, 0.25))
713 );
714 assert_eq!(
715 animation.frame_uv_rect(2),
716 Some(Rect::new(0.25, 0.0, 0.25, 0.25))
717 );
718 assert_eq!(
719 animation.frame_uv_rect(3),
720 Some(Rect::new(0.25, 0.25, 0.25, 0.25))
721 );
722 }
723
724 #[test]
725 fn test_sprite_sheet_playback() {
726 let mut animation =
727 SpriteSheetAnimation::<MyTexture>::new_from_image_parameters(ImageParameters {
728 width: 128,
729 height: 128,
730 frame_width: 32,
731 frame_height: 32,
732 first_frame: 2,
733 last_frame: 6,
734 column_major: true,
735 });
736
737 animation.speed = 1.0; animation.looping = false;
739
740 assert_eq!(animation.status, Status::Stopped);
741
742 animation.play();
743
744 assert_eq!(animation.status, Status::Playing);
745
746 let expected_output = [
747 Rect::new(0.0, 0.5, 0.25, 0.25),
748 Rect::new(0.0, 0.75, 0.25, 0.25),
749 Rect::new(0.25, 0.0, 0.25, 0.25),
750 Rect::new(0.25, 0.25, 0.25, 0.25),
751 ];
752
753 for &expected_frame in &expected_output {
754 assert_eq!(animation.current_frame_uv_rect(), Some(expected_frame));
755 animation.update(1.0);
756 }
757
758 assert_eq!(animation.status, Status::Stopped);
759
760 animation.speed = -1.0; animation.play();
763
764 for &expected_frame in expected_output.iter().rev() {
765 assert_eq!(animation.current_frame_uv_rect(), Some(expected_frame));
766 animation.update(1.0);
767 }
768 }
769
770 #[test]
771 fn test_signals() {
772 let mut animation = SpriteSheetAnimation::<MyTexture>::new();
773
774 animation.add_frame(Vector2::new(0, 0));
775 animation.add_frame(Vector2::new(1, 0));
776 animation.add_frame(Vector2::new(2, 0));
777
778 animation.set_speed(1.0);
779 animation.set_looping(false);
780 animation.play();
781
782 animation.add_signal(Signal {
783 id: 0,
784 frame: 1,
785 enabled: true,
786 });
787
788 animation.add_signal(Signal {
789 id: 1,
790 frame: 1,
791 enabled: false,
792 });
793
794 animation.add_signal(Signal {
795 id: 2,
796 frame: 2,
797 enabled: true,
798 });
799
800 for _ in 0..3 {
801 animation.update(1.0);
802 }
803
804 assert_eq!(animation.pop_event(), Some(Event::Signal(0)));
805 assert_eq!(animation.pop_event(), Some(Event::Signal(2)));
807 assert_eq!(animation.pop_event(), None);
809 }
810}