use std::time::Duration;
use bevy::prelude::*;
use crate::{
CRATE_NAME,
animation::{Animation, AnimationDirection, AnimationDuration, AnimationRepeat},
clip::{Clip, ClipId},
easing::Easing,
events::Marker,
};
#[derive(Debug, Clone, Reflect)]
#[reflect(Debug)]
pub(crate) struct CacheFrame {
pub atlas_index: usize,
pub duration: Duration,
pub clip_id: ClipId,
pub clip_repetition: usize,
pub events: Vec<AnimationCacheEvent>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Reflect)]
#[reflect(Debug, PartialEq, Hash)]
pub(crate) enum AnimationCacheEvent {
MarkerHit {
marker: Marker,
clip_id: ClipId,
clip_repetition: usize,
},
ClipRepetitionEnd {
clip_id: ClipId,
clip_repetition: usize,
},
ClipEnd {
clip_id: ClipId,
},
}
#[derive(Debug, Reflect)]
#[reflect(Debug)]
pub(crate) struct AnimationCache {
pub frames: Vec<CacheFrame>,
pub frames_pong: Option<Vec<CacheFrame>>,
pub repetitions: Option<usize>,
pub animation_direction: AnimationDirection,
}
impl AnimationCache {
fn empty() -> Self {
Self {
frames: Vec::new(),
frames_pong: None,
repetitions: None,
animation_direction: AnimationDirection::Forwards,
}
}
pub fn from_animation(animation: &Animation) -> AnimationCache {
let animation_repetitions = animation.repetitions().unwrap_or_default();
if matches!(animation_repetitions, AnimationRepeat::Times(0)) {
return Self::empty();
}
let clips_data = animation
.clips()
.iter()
.map(ClipData::new)
.filter(|data| {
!data.clip.atlas_indices().is_empty()
&& data.repetitions > 0
&& data.duration_with_repetitions_ms > 0
});
let animation_duration_ms: u32 = clips_data
.clone()
.map(|data| data.duration_with_repetitions_ms)
.sum();
if animation_duration_ms == 0 {
return Self::empty();
}
let clip_frames = clips_data
.map(|clip_data| {
let clip_corrected_duration = match animation.duration() {
None => clip_data.duration,
Some(AnimationDuration::PerFrame(animation_frame_duration)) => {
AnimationDuration::PerFrame(*animation_frame_duration)
}
Some(AnimationDuration::PerRepetition(animation_cycle_duration)) => {
let clip_ratio = clip_data.duration_with_repetitions_ms as f32
/ animation_duration_ms as f32;
AnimationDuration::PerRepetition(
(*animation_cycle_duration as f32 * clip_ratio
/ clip_data.repetitions as f32) as u32,
)
}
};
let clip_frame_corrected_duration_ms = match clip_corrected_duration {
AnimationDuration::PerFrame(frame_duration_ms) => frame_duration_ms,
AnimationDuration::PerRepetition(cycle_duration_ms) => {
cycle_duration_ms / clip_data.clip.atlas_indices().len() as u32
}
};
ClipFrames::new(clip_data, clip_frame_corrected_duration_ms)
})
.collect();
let animation_frames = AnimationFrames::new(clip_frames);
let animation_direction = animation.direction().unwrap_or_default();
let animation_easing = animation.easing().unwrap_or_default();
let (all_frames, all_frames_pong) =
animation_frames.build(animation_direction, animation_easing);
let animation_repetition_count = match animation_repetitions {
AnimationRepeat::Loop => None,
AnimationRepeat::Times(n) => Some(n),
};
Self {
frames: all_frames,
frames_pong: all_frames_pong,
repetitions: animation_repetition_count,
animation_direction,
}
}
}
#[derive(Clone)]
struct ClipData {
clip: Clip,
duration: AnimationDuration,
repetitions: usize,
direction: AnimationDirection,
easing: Easing,
duration_with_repetitions_ms: u32,
}
impl ClipData {
fn new(clip: &Clip) -> Self {
let duration = clip.duration().unwrap_or_default();
let repetitions = clip.repetitions().unwrap_or(1);
let direction = clip.direction().unwrap_or_default();
let easing = clip.easing().unwrap_or_default();
let frame_count_with_repetitions = match direction {
AnimationDirection::Forwards | AnimationDirection::Backwards => {
clip.atlas_indices().len() as u32 * repetitions as u32
}
AnimationDirection::PingPong => {
clip.atlas_indices().len().saturating_sub(1) as u32 * repetitions as u32 + 1
}
};
let duration_with_repetitions_ms = match duration {
AnimationDuration::PerFrame(frame_duration) => {
frame_duration * frame_count_with_repetitions
}
AnimationDuration::PerRepetition(repetition_duration) => repetition_duration,
};
Self {
clip: clip.clone(),
duration,
repetitions,
direction,
easing,
duration_with_repetitions_ms,
}
}
}
#[derive(Clone)]
struct Frame {
atlas_index: usize,
duration: Duration,
markers: Vec<Marker>,
}
#[derive(Clone)]
struct ClipRepetitionFrames {
frames: Vec<Frame>,
}
impl ClipRepetitionFrames {
fn new(clip_data: &ClipData, frame_duration_ms: u32) -> Self {
Self {
frames: clip_data
.clip
.atlas_indices()
.iter()
.enumerate()
.map(move |(frame_index, frame_atlas_index)| {
let markers = clip_data
.clip
.markers()
.get(&frame_index)
.cloned()
.unwrap_or(Vec::new());
Frame {
atlas_index: *frame_atlas_index,
duration: Duration::from_millis(frame_duration_ms as u64),
markers,
}
})
.filter(|frame| !frame.duration.is_zero())
.collect(),
}
}
fn backwards(&self) -> Self {
Self {
frames: self.frames.iter().rev().cloned().collect(),
}
}
fn ping(&self) -> Self {
Self {
frames: self.frames.iter().skip(1).cloned().collect(),
}
}
fn pong(&self) -> Self {
Self {
frames: self.frames.iter().rev().skip(1).cloned().collect(),
}
}
}
#[derive(Clone)]
struct ClipFrames {
repetitions: Vec<ClipRepetitionFrames>,
data: ClipData,
}
impl ClipFrames {
fn new(clip_data: ClipData, frame_duration_override_ms: u32) -> Self {
let reference_repetition =
ClipRepetitionFrames::new(&clip_data, frame_duration_override_ms);
Self {
repetitions: (0..clip_data.repetitions)
.map(|repetition| {
match clip_data.direction {
AnimationDirection::Forwards => reference_repetition.clone(),
AnimationDirection::Backwards => reference_repetition.backwards(),
AnimationDirection::PingPong => {
if repetition == 0 {
reference_repetition.clone()
} else if repetition % 2 == 0 {
reference_repetition.ping()
} else {
reference_repetition.pong()
}
}
}
})
.filter(|repetition| !repetition.frames.is_empty())
.collect(),
data: clip_data,
}
}
fn backwards(&self) -> Self {
Self {
repetitions: self
.repetitions
.iter()
.map(|clip| clip.backwards())
.rev()
.collect(),
data: self.data.clone(),
}
}
}
#[derive(Default, Clone)]
struct AnimationFrames {
clips: Vec<ClipFrames>,
}
impl AnimationFrames {
fn new(clips: Vec<ClipFrames>) -> Self {
Self {
clips: clips
.iter()
.filter(|clip| !clip.repetitions.is_empty())
.cloned()
.collect(),
}
}
fn backwards(&self) -> Self {
Self {
clips: self
.clips
.iter()
.map(|clip| clip.backwards())
.rev()
.collect(),
}
}
fn build(
&self,
direction: AnimationDirection,
easing: Easing,
) -> (Vec<CacheFrame>, Option<Vec<CacheFrame>>) {
let (animation_frames, animation_frames_pong) = match direction {
AnimationDirection::Forwards => (self.clone(), None),
AnimationDirection::Backwards => (self.backwards(), None),
AnimationDirection::PingPong => (self.clone(), Some(self.backwards())),
};
let merge = |mut frames: AnimationFrames| {
let mut all_frames = Vec::new();
let mut previous_clip_id = None;
let mut previous_clip_repetition = None;
for clip in &mut frames.clips {
let mut all_clip_frames = Vec::new();
for (repetition_index, repetition) in clip.repetitions.iter_mut().enumerate() {
let clip_frame_durations = repetition
.frames
.iter_mut()
.map(|frame| &mut frame.duration)
.collect();
apply_easing(clip_frame_durations, clip.data.easing);
let mut clip_frames: Vec<_> = repetition
.frames
.iter()
.map(|frame| CacheFrame {
atlas_index: frame.atlas_index,
duration: frame.duration,
clip_id: clip.data.clip.id(),
clip_repetition: repetition_index,
events: frame
.markers
.iter()
.map(|marker| AnimationCacheEvent::MarkerHit {
marker: *marker,
clip_id: clip.data.clip.id(),
clip_repetition: repetition_index,
})
.collect(),
})
.collect();
if let Some((previous_clip_id, previous_clip_repetition)) =
previous_clip_repetition
{
clip_frames[0]
.events
.push(AnimationCacheEvent::ClipRepetitionEnd {
clip_id: previous_clip_id,
clip_repetition: previous_clip_repetition,
});
}
previous_clip_repetition = Some((clip.data.clip.id(), repetition_index));
all_clip_frames.extend(clip_frames);
}
if let Some(previous_clip_id) = previous_clip_id {
all_clip_frames[0]
.events
.push(AnimationCacheEvent::ClipEnd {
clip_id: previous_clip_id,
});
}
previous_clip_id = Some(clip.data.clip.id());
all_frames.extend(all_clip_frames);
}
let animation_frame_durations = all_frames
.iter_mut()
.map(|frame| &mut frame.duration)
.collect();
apply_easing(animation_frame_durations, easing);
all_frames
};
(merge(animation_frames), animation_frames_pong.map(merge))
}
}
fn apply_easing(frame_durations: Vec<&mut Duration>, easing: Easing) {
if matches!(easing, Easing::Linear) {
return;
}
let total_duration_ms: u32 = frame_durations.iter().map(|d| d.as_millis() as u32).sum();
if total_duration_ms == 0 {
error!("{CRATE_NAME}: zero duration, cannot apply easing");
return;
}
let mut accumulated_time = 0;
let mut previous_eased_time = 0.0;
for frame_duration in frame_durations {
let normalized_time = accumulated_time as f32 / total_duration_ms as f32;
let normalized_eased_time = easing.get(normalized_time);
let eased_time = normalized_eased_time * total_duration_ms as f32;
let eased_duration = (eased_time - previous_eased_time) as u32;
accumulated_time += frame_duration.as_millis();
previous_eased_time = eased_time;
*frame_duration = Duration::from_millis(eased_duration as u64);
}
}