Galactica/crates/content/src/spriteautomaton.rs

309 lines
8.5 KiB
Rust

use crate::{SectionEdge, Sprite, SpriteSection, StartEdge};
use std::sync::Arc;
/// A single frame's state
#[derive(Debug, Clone)]
pub struct AnimationState {
/// The index of the texture we're fading from
pub texture_a: u32,
/// The index of the texture we're fading to
pub texture_b: u32,
/// Between 0.0 and 1.0, denoting how far we are between
/// texture_a and texture_b
/// 0.0 means fully show texture_a;
/// 1.0 means fully show texture_b.
pub fade: f32,
}
impl AnimationState {
/// Convenience method.
/// Get texture index as an array
pub fn texture_index(&self) -> [u32; 2] {
[self.texture_a, self.texture_b]
}
}
/// What direction are we playing our animation in?
#[derive(Debug, Clone, Copy)]
enum AnimDirection {
/// Top to bottom, with increasing frame indices
/// (normal)
Up,
/// Bottom to top, with decreasing frame indices
/// (reverse)
Down,
/// Stopped on one frame
Stop,
}
/// Manages a single sprite's animation state.
#[derive(Debug, Clone)]
pub struct SpriteAutomaton {
/// The sprite we're animating
sprite: Arc<Sprite>,
/// Which animation section we're on
/// This MUST be a section from this Automaton's sprite
current_section: Arc<SpriteSection>,
/// Which frame we're on
current_frame: usize,
/// Where we are between frames.
current_edge_progress: f32,
/// The length of the current edge we're on, in seconds
current_edge_duration: f32,
/// In what direction are we playing the current section?
current_direction: AnimDirection,
/// The texture we're fading from
/// (if we're moving downwards)
last_texture: u32,
/// The texture we're fading to
/// (if we're moving downwards)
next_texture: u32,
/// If this is some, take this edge next
next_edge_override: Option<SectionEdge>,
}
impl SpriteAutomaton {
/// Create a new AnimAutomaton
pub fn new(sprite: Arc<Sprite>) -> Self {
let (current_section, texture, current_direction) = {
match &sprite.start_at {
StartEdge::Top { section } => (
section,
*section.frames.first().unwrap(),
AnimDirection::Down,
),
StartEdge::Bot { section } => {
(section, *section.frames.last().unwrap(), AnimDirection::Up)
}
}
};
Self {
sprite: sprite.clone(),
current_frame: 0,
current_edge_progress: match current_direction {
AnimDirection::Down => 0.0,
AnimDirection::Up => current_section.frame_duration,
AnimDirection::Stop => unreachable!("how'd you get here?"),
},
current_edge_duration: current_section.frame_duration,
next_edge_override: None,
current_direction,
current_section: current_section.clone(),
last_texture: texture,
next_texture: texture,
}
}
/// Override the next section transition
pub fn next_edge(&mut self, s: SectionEdge) {
self.next_edge_override = Some(s);
}
/// Force a transition to the given section right now
pub fn jump_to(&mut self, start: &SectionEdge) {
self.take_edge(start);
}
fn take_edge(&mut self, e: &SectionEdge) {
let last = match self.current_direction {
AnimDirection::Stop => self.next_texture,
AnimDirection::Down => self.next_texture,
AnimDirection::Up => self.last_texture,
};
match e {
SectionEdge::Stop => {
match self.current_direction {
AnimDirection::Stop => {}
AnimDirection::Up => {
self.current_frame = 0;
}
AnimDirection::Down => {
self.current_frame = self.current_section.frames.len() - 1;
}
}
self.current_edge_duration = 1.0;
self.current_direction = AnimDirection::Stop;
}
SectionEdge::Top { section, duration } => {
self.current_section = section.clone();
self.current_edge_duration = *duration;
self.current_frame = 0;
self.current_direction = AnimDirection::Down;
}
SectionEdge::Bot { section, duration } => {
self.current_section = section.clone();
self.current_frame = section.frames.len() - 1;
self.current_edge_duration = *duration;
self.current_direction = AnimDirection::Up;
}
SectionEdge::Repeat { duration } => {
match self.current_direction {
AnimDirection::Stop => {}
AnimDirection::Up => {
self.current_frame = self.current_section.frames.len() - 1;
}
AnimDirection::Down => {
self.current_frame = 0;
}
}
self.current_edge_duration = *duration;
}
SectionEdge::Reverse { duration } => {
match self.current_direction {
AnimDirection::Stop => {}
AnimDirection::Up => {
// Jump to SECOND frame, since we've already shown the
// first during the fade transition
self.current_frame = {
if self.current_section.frames.len() == 1 {
0
} else {
1
}
};
self.current_direction = AnimDirection::Down;
}
AnimDirection::Down => {
self.current_frame = {
if self.current_section.frames.len() == 1 {
0
} else {
self.current_section.frames.len() - 2
}
};
self.current_direction = AnimDirection::Up;
}
}
self.current_edge_duration = *duration;
}
}
match self.current_direction {
AnimDirection::Stop => {
self.next_texture = self.current_section.frames[self.current_frame];
self.last_texture = self.current_section.frames[self.current_frame];
}
AnimDirection::Down => {
self.last_texture = last;
self.next_texture = self.current_section.frames[self.current_frame];
self.current_edge_progress = 0.0;
}
AnimDirection::Up => {
self.next_texture = last;
self.last_texture = self.current_section.frames[self.current_frame];
self.current_edge_progress = self.current_edge_duration;
}
}
}
/// Step this animation by `t` seconds
pub fn step(&mut self, t: f32) {
// Current_fade and current_frame keep track of where we are in the current section.
// current_frame indexes this section frames. When it exceeds the number of frames
// or falls below zero (when moving in reverse), we switch to the next section.
//
// current_fade keeps track of our state between frames. It is zero once a frame starts,
// and we switch to the next frame when it hits 1.0. If we are stepping foward, it increases,
// and if we are stepping backwards, it decreases.
// Note that frame_duration may be zero!
// This is only possible in the hidden texture, since
// user-provided sections are always checked to be positive.
assert!(self.current_section.frame_duration >= 0.0);
match self.current_direction {
AnimDirection::Stop => {
// Edge case: we're stopped and got a request to transition.
// we should transition right away.
if self.next_edge_override.is_some() {
let e = self.next_edge_override.take().unwrap();
self.take_edge(&e);
}
return;
}
AnimDirection::Down => {
self.current_edge_progress += t;
// We're stepping foward and finished this frame
if self.current_edge_progress > self.current_edge_duration {
if self.current_frame < self.current_section.frames.len() - 1 {
self.current_frame += 1;
self.last_texture = self.next_texture;
self.next_texture = self.current_section.frames[self.current_frame];
self.current_edge_progress = 0.0;
self.current_edge_duration = self.current_section.frame_duration;
} else {
let e = {
if self.next_edge_override.is_some() {
self.next_edge_override.take().unwrap()
} else {
self.current_section.edge_bot.clone()
}
};
self.take_edge(&e);
}
}
}
AnimDirection::Up => {
self.current_edge_progress -= t;
// We're stepping backward and finished this frame
if self.current_edge_progress < 0.0 {
if self.current_frame > 0 {
self.current_frame -= 1;
self.next_texture = self.last_texture;
self.last_texture = self.current_section.frames[self.current_frame];
self.current_edge_progress = self.current_section.frame_duration;
self.current_edge_duration = self.current_section.frame_duration;
} else {
let e = {
if self.next_edge_override.is_some() {
self.next_edge_override.take().unwrap()
} else {
self.current_section.edge_top.clone()
}
};
self.take_edge(&e);
}
}
}
}
}
/// Get the current frame of this animation
pub fn get_texture_idx(&self) -> AnimationState {
return AnimationState {
texture_a: self.last_texture,
texture_b: self.next_texture,
fade: self.current_edge_progress / self.current_edge_duration,
};
}
/// Get the sprite this automaton is using
pub fn get_sprite(&self) -> Arc<Sprite> {
self.sprite.clone()
}
}