use crate::interaction::select::selection::NodeId;
use crate::runtime::context::RuntimeStepContext;
use crate::runtime::plugin::{RuntimePlugin, phase};
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Keyframe {
pub time: f32,
pub transform: glam::Affine3A,
}
#[derive(Debug, Clone)]
pub struct AnimationTrack {
pub node_id: NodeId,
pub keyframes: Vec<Keyframe>,
pub looping: bool,
}
impl AnimationTrack {
pub fn duration(&self) -> f32 {
self.keyframes.last().map_or(0.0, |k| k.time)
}
pub fn sample(&self, mut time: f32) -> Option<glam::Affine3A> {
if self.keyframes.is_empty() {
return None;
}
if self.keyframes.len() == 1 {
return Some(self.keyframes[0].transform);
}
let dur = self.duration();
if self.looping && dur > 1e-6 {
time = time.rem_euclid(dur);
} else {
time = time.clamp(0.0, dur);
}
let upper = self.keyframes.partition_point(|k| k.time <= time);
let idx = upper.saturating_sub(1).min(self.keyframes.len() - 2);
let a = &self.keyframes[idx];
let b = &self.keyframes[idx + 1];
let span = b.time - a.time;
let t = if span > 1e-6 {
(time - a.time) / span
} else {
0.0
};
let (sa, ra, ta) = a.transform.to_scale_rotation_translation();
let (sb, rb, tb) = b.transform.to_scale_rotation_translation();
let s = sa.lerp(sb, t);
let r = ra.slerp(rb, t);
let p = ta.lerp(tb, t);
Some(glam::Affine3A::from_scale_rotation_translation(s, r, p))
}
}
pub struct AnimationPlugin {
tracks: Vec<AnimationTrack>,
time: f32,
playing: bool,
speed: f32,
}
impl Default for AnimationPlugin {
fn default() -> Self {
Self::new()
}
}
impl AnimationPlugin {
pub fn new() -> Self {
Self {
tracks: Vec::new(),
time: 0.0,
playing: true,
speed: 1.0,
}
}
pub fn add_track(&mut self, track: AnimationTrack) {
self.tracks.push(track);
}
pub fn play(&mut self) {
self.playing = true;
}
pub fn pause(&mut self) {
self.playing = false;
}
pub fn reset(&mut self) {
self.time = 0.0;
}
pub fn set_speed(&mut self, speed: f32) {
self.speed = speed;
}
pub fn time(&self) -> f32 {
self.time
}
pub fn is_playing(&self) -> bool {
self.playing
}
pub fn duration(&self) -> f32 {
self.tracks
.iter()
.map(|t| t.duration())
.fold(0.0_f32, f32::max)
}
}
impl RuntimePlugin for AnimationPlugin {
fn priority(&self) -> i32 {
phase::ANIMATE
}
fn step(&mut self, ctx: &mut RuntimeStepContext<'_>) {
if self.playing {
self.time += ctx.dt * self.speed;
}
for track in &self.tracks {
if let Some(transform) = track.sample(self.time) {
ctx.writeback.set(track.node_id, transform);
}
}
}
}