///|
pub(all) struct AnimationFrameRef {
name : String
frame_index : Int
duration_ms : Int
rect : Rect
} derive(Eq, @debug.Debug)
///|
pub fn AnimationFrameRef::from_frame(frame : FrameInfo) -> AnimationFrameRef {
{
name: frame.name,
frame_index: frame.index,
duration_ms: frame.duration_ms,
rect: frame.rect,
}
}
///|
pub(all) struct AnimationClip {
name : String
direction : TagDirection
loop_mode : LoopMode
frames : Array[AnimationFrameRef]
total_duration_ms : Int
} derive(Eq, @debug.Debug)
///|
pub fn AnimationClip::new(
name : String,
frames : Array[AnimationFrameRef],
direction? : TagDirection = DirectionForward,
loop_mode? : LoopMode = LoopRepeat,
) -> AnimationClip {
let total = for frame in frames; sum = 0 {
continue sum + frame.duration_ms
} nobreak {
sum
}
{ name, direction, loop_mode, frames, total_duration_ms: total }
}
///|
pub fn AnimationClip::is_valid(self : AnimationClip) -> Bool {
self.name != "" && self.frames.length() > 0 && self.total_duration_ms > 0
}
///|
pub fn AnimationClip::frame_count(self : AnimationClip) -> Int {
self.frames.length()
}
///|
pub fn AnimationClip::frame_at(
self : AnimationClip,
index : Int,
) -> AnimationFrameRef? {
if index >= 0 && index < self.frames.length() {
Some(self.frames[index])
} else {
None
}
}
///|
pub(all) struct AnimationLibrary {
texture_image : String
atlas_size : Size
clips : Array[AnimationClip]
} derive(Eq, @debug.Debug)
///|
pub fn AnimationLibrary::empty() -> AnimationLibrary {
{ texture_image: "", atlas_size: Size::empty(), clips: [] }
}
///|
pub fn AnimationLibrary::find_clip(
self : AnimationLibrary,
name : String,
) -> AnimationClip? {
for clip in self.clips {
if clip.name == name {
return Some(clip)
}
}
None
}
///|
pub fn build_animation_library(sheet : SpriteSheet) -> AnimationLibrary {
let clips : Array[AnimationClip] = []
if sheet.meta.frame_tags.length() == 0 {
clips.push(build_all_frames_clip(sheet))
} else {
for tag in sheet.meta.frame_tags {
clips.push(build_clip_from_tag(sheet, tag))
}
}
{ texture_image: sheet.meta.image, atlas_size: sheet.meta.size, clips }
}
///|
fn build_all_frames_clip(sheet : SpriteSheet) -> AnimationClip {
let refs : Array[AnimationFrameRef] = []
for frame in sheet.frames {
refs.push(AnimationFrameRef::from_frame(frame))
}
AnimationClip::new("all", refs)
}
///|
fn build_clip_from_tag(sheet : SpriteSheet, tag : FrameTag) -> AnimationClip {
let refs : Array[AnimationFrameRef] = []
if tag.direction == DirectionReverse ||
tag.direction == DirectionPingPongReverse {
for i in tag.to_index>..tag.from_index {
match sheet.frame_at(i - 1) {
Some(frame) => refs.push(AnimationFrameRef::from_frame(frame))
None => ()
}
}
} else {
for i in tag.from_index..<=tag.to_index {
match sheet.frame_at(i) {
Some(frame) => refs.push(AnimationFrameRef::from_frame(frame))
None => ()
}
}
}
let mode = if tag.direction.is_pingpong() { LoopPingPong } else { LoopRepeat }
AnimationClip::new(tag.name, refs, direction=tag.direction, loop_mode=mode)
}
///|
pub(all) struct AnimationPlayer {
library : AnimationLibrary
clip_name : String
frame_cursor : Int
elapsed_in_frame_ms : Int
playing : Bool
completed : Bool
forward : Bool
} derive(Eq, @debug.Debug)
///|
pub fn AnimationPlayer::new(
library : AnimationLibrary,
clip_name? : String = "",
) -> AnimationPlayer {
let chosen = if clip_name != "" {
clip_name
} else if library.clips.length() > 0 {
library.clips[0].name
} else {
""
}
{
library,
clip_name: chosen,
frame_cursor: 0,
elapsed_in_frame_ms: 0,
playing: true,
completed: false,
forward: true,
}
}
///|
pub fn AnimationPlayer::current_clip(self : AnimationPlayer) -> AnimationClip? {
self.library.find_clip(self.clip_name)
}
///|
pub fn AnimationPlayer::current_frame(
self : AnimationPlayer,
) -> AnimationFrameRef? {
match self.current_clip() {
Some(clip) => clip.frame_at(self.frame_cursor)
None => None
}
}
///|
pub fn AnimationPlayer::play(self : AnimationPlayer) -> AnimationPlayer {
{ ..self, playing: true, completed: false }
}
///|
pub fn AnimationPlayer::pause(self : AnimationPlayer) -> AnimationPlayer {
{ ..self, playing: false }
}
///|
pub fn AnimationPlayer::reset(self : AnimationPlayer) -> AnimationPlayer {
{
..self,
frame_cursor: 0,
elapsed_in_frame_ms: 0,
playing: true,
completed: false,
forward: true,
}
}
///|
pub fn AnimationPlayer::set_clip(
self : AnimationPlayer,
clip_name : String,
) -> AnimationPlayer {
if self.clip_name == clip_name {
self
} else {
{
..self,
clip_name,
frame_cursor: 0,
elapsed_in_frame_ms: 0,
playing: true,
completed: false,
forward: true,
}
}
}
///|
pub fn AnimationPlayer::step(
self : AnimationPlayer,
delta_ms : Int,
) -> AnimationPlayer {
if delta_ms <= 0 || !self.playing || self.completed {
self
} else {
match self.current_clip() {
None => self
Some(clip) => step_with_clip(self, clip, delta_ms)
}
}
}
///|
fn step_with_clip(
player : AnimationPlayer,
clip : AnimationClip,
delta_ms : Int,
) -> AnimationPlayer {
if clip.frames.length() == 0 {
{ ..player, playing: false, completed: true }
} else {
let current = clip.frames[player.frame_cursor]
let elapsed = player.elapsed_in_frame_ms + delta_ms
if elapsed < current.duration_ms {
{ ..player, elapsed_in_frame_ms: elapsed }
} else {
advance_player_frame(
{ ..player, elapsed_in_frame_ms: elapsed - current.duration_ms },
clip,
)
}
}
}
///|
fn advance_player_frame(
player : AnimationPlayer,
clip : AnimationClip,
) -> AnimationPlayer {
match clip.loop_mode {
LoopOnce => advance_once(player, clip)
LoopRepeat => advance_repeat(player, clip)
LoopPingPong => advance_pingpong(player, clip)
}
}
///|
fn advance_once(
player : AnimationPlayer,
clip : AnimationClip,
) -> AnimationPlayer {
if player.frame_cursor + 1 >= clip.frames.length() {
{
..player,
frame_cursor: clip.frames.length() - 1,
playing: false,
completed: true,
}
} else {
{ ..player, frame_cursor: player.frame_cursor + 1 }
}
}
///|
fn advance_repeat(
player : AnimationPlayer,
clip : AnimationClip,
) -> AnimationPlayer {
let next = if player.frame_cursor + 1 >= clip.frames.length() {
0
} else {
player.frame_cursor + 1
}
{ ..player, frame_cursor: next }
}
///|
fn advance_pingpong(
player : AnimationPlayer,
clip : AnimationClip,
) -> AnimationPlayer {
if clip.frames.length() <= 1 {
player
} else if player.forward && player.frame_cursor + 1 >= clip.frames.length() {
{ ..player, frame_cursor: player.frame_cursor - 1, forward: false }
} else if !player.forward && player.frame_cursor <= 0 {
{ ..player, frame_cursor: 1, forward: true }
} else if player.forward {
{ ..player, frame_cursor: player.frame_cursor + 1 }
} else {
{ ..player, frame_cursor: player.frame_cursor - 1 }
}
}
///|
pub fn sample_clip_at(
clip : AnimationClip,
elapsed_ms : Int,
) -> AnimationFrameRef? {
if !clip.is_valid() || elapsed_ms < 0 {
None
} else {
let wrapped = if clip.total_duration_ms <= 0 {
0
} else {
elapsed_ms % clip.total_duration_ms
}
let mut acc = 0
for frame in clip.frames {
acc = acc + frame.duration_ms
if wrapped < acc {
return Some(frame)
}
}
clip.frame_at(clip.frames.length() - 1)
}
}
///|
pub fn estimate_clip_fps(clip : AnimationClip) -> Int {
if clip.total_duration_ms <= 0 {
0
} else {
clip.frames.length() * 1000 / clip.total_duration_ms
}
}