Skip to main content

pfx_core/anim/
animator.rs

1use std::sync::Arc;
2
3use super::AnimError;
4use super::math::Mat4;
5use super::pose::Pose;
6use super::skeleton::Rig;
7
8pub const MAX_MIXES: usize = 4;
9pub const MAX_REPEATS: u64 = 64;
10
11#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
12pub enum Playback {
13    #[default]
14    Loop,
15    Once,
16    PingPong,
17}
18
19#[derive(Clone, Debug, PartialEq)]
20pub struct Fired {
21    pub clip: String,
22    pub event: String,
23    pub time: f32,
24    pub tick: u64,
25}
26
27#[derive(Clone, Debug, PartialEq)]
28struct Layer {
29    clip: usize,
30    start: u64,
31    offset: f64,
32    begin: f64,
33    speed: f32,
34    weight: f32,
35    playback: Playback,
36    heard: Option<f64>,
37}
38
39impl Layer {
40    fn new(clip: usize, tick: u64) -> Self {
41        Self {
42            clip,
43            start: tick,
44            offset: 0.0,
45            begin: 0.0,
46            speed: 1.0,
47            weight: 1.0,
48            playback: Playback::Loop,
49            heard: None,
50        }
51    }
52
53    fn elapsed(&self, tick: u64, rate: u32) -> f64 {
54        let ticks = tick.saturating_sub(self.start) as f64;
55        self.offset + ticks * self.speed as f64 / rate as f64
56    }
57
58    fn rebase(&mut self, tick: u64, rate: u32) {
59        self.offset = self.elapsed(tick, rate);
60        self.start = tick.max(self.start);
61    }
62}
63
64fn local_time(elapsed: f64, duration: f32, playback: Playback) -> f32 {
65    let duration = duration as f64;
66    let time = match playback {
67        Playback::Once => elapsed.min(duration),
68        _ if duration <= 0.0 => 0.0,
69        Playback::Loop => elapsed - (elapsed / duration).floor() * duration,
70        Playback::PingPong => {
71            let period = 2.0 * duration;
72            let at = elapsed - (elapsed / period).floor() * period;
73            if at > duration { period - at } else { at }
74        }
75    };
76    time as f32
77}
78
79#[derive(Clone, Debug, PartialEq)]
80struct Mix {
81    layers: Vec<Layer>,
82    start: u64,
83    fade: u64,
84}
85
86impl Mix {
87    fn progress(&self, tick: u64) -> f32 {
88        if self.fade == 0 {
89            return 1.0;
90        }
91        let done = tick.saturating_sub(self.start).min(self.fade);
92        (done as f64 / self.fade as f64) as f32
93    }
94}
95
96#[derive(Clone, Debug, PartialEq)]
97pub struct Animator {
98    rig: Arc<Rig>,
99    rate: u32,
100    mixes: Vec<Mix>,
101}
102
103impl Animator {
104    pub fn new(rig: Arc<Rig>, rate: u32) -> Self {
105        Self {
106            rig,
107            rate: rate.max(1),
108            mixes: Vec::new(),
109        }
110    }
111
112    pub fn rig(&self) -> &Rig {
113        &self.rig
114    }
115
116    pub fn shared_rig(&self) -> &Arc<Rig> {
117        &self.rig
118    }
119
120    pub fn rate(&self) -> u32 {
121        self.rate
122    }
123
124    fn find(&self, clip: &str) -> Result<usize, AnimError> {
125        self.rig
126            .clip(clip)
127            .ok_or_else(|| AnimError::UnknownClip(clip.to_string()))
128    }
129
130    fn layer_mut(&mut self, clip: &str) -> Result<&mut Layer, AnimError> {
131        let index = self.find(clip)?;
132        self.mixes
133            .last_mut()
134            .and_then(|mix| mix.layers.iter_mut().find(|layer| layer.clip == index))
135            .ok_or_else(|| AnimError::NotPlaying(clip.to_string()))
136    }
137
138    pub fn play(&mut self, clip: &str, tick: u64) -> Result<(), AnimError> {
139        self.crossfade(clip, 0.0, tick)
140    }
141
142    pub fn crossfade(&mut self, clip: &str, seconds: f32, tick: u64) -> Result<(), AnimError> {
143        let index = self.find(clip)?;
144        let fade = fade_ticks(seconds, self.rate)?;
145        self.mixes.push(Mix {
146            layers: vec![Layer::new(index, tick)],
147            start: tick,
148            fade,
149        });
150        if self.mixes.len() > MAX_MIXES {
151            let extra = self.mixes.len() - MAX_MIXES;
152            self.mixes.drain(..extra);
153        }
154        Ok(())
155    }
156
157    pub fn set_fade(&mut self, seconds: f32) -> Result<(), AnimError> {
158        let fade = fade_ticks(seconds, self.rate)?;
159        if let Some(mix) = self.mixes.last_mut() {
160            mix.fade = fade;
161        }
162        Ok(())
163    }
164
165    pub fn blend(&mut self, clip: &str, weight: f32, tick: u64) -> Result<(), AnimError> {
166        let index = self.find(clip)?;
167        let weight = check_weight(clip, weight)?;
168        let Some(mix) = self.mixes.last_mut() else {
169            self.mixes.push(Mix {
170                layers: vec![Layer {
171                    weight,
172                    ..Layer::new(index, tick)
173                }],
174                start: tick,
175                fade: 0,
176            });
177            return Ok(());
178        };
179        match mix.layers.iter_mut().find(|layer| layer.clip == index) {
180            Some(layer) => layer.weight = weight,
181            None => mix.layers.push(Layer {
182                weight,
183                ..Layer::new(index, tick)
184            }),
185        }
186        Ok(())
187    }
188
189    pub fn set_weight(&mut self, clip: &str, weight: f32) -> Result<(), AnimError> {
190        let weight = check_weight(clip, weight)?;
191        self.layer_mut(clip)?.weight = weight;
192        Ok(())
193    }
194
195    pub fn set_speed(&mut self, clip: &str, speed: f32, tick: u64) -> Result<(), AnimError> {
196        if !speed.is_finite() || speed < 0.0 {
197            return Err(AnimError::Speed(speed));
198        }
199        let rate = self.rate;
200        let layer = self.layer_mut(clip)?;
201        layer.rebase(tick, rate);
202        layer.speed = speed;
203        Ok(())
204    }
205
206    pub fn set_start(&mut self, clip: &str, seconds: f32, tick: u64) -> Result<(), AnimError> {
207        if !seconds.is_finite() || seconds < 0.0 {
208            return Err(AnimError::Start(seconds));
209        }
210        let layer = self.layer_mut(clip)?;
211        layer.start = tick;
212        layer.offset = seconds as f64;
213        layer.begin = seconds as f64;
214        layer.heard = None;
215        Ok(())
216    }
217
218    pub fn play_blend(&mut self, clips: &[(&str, f32)], tick: u64) -> Result<(), AnimError> {
219        let Some((first, _)) = clips.first() else {
220            return Err(AnimError::UnknownClip(String::new()));
221        };
222        for (clip, weight) in clips {
223            self.find(clip)?;
224            if !weight.is_finite() || *weight < 0.0 {
225                return Err(AnimError::Weight {
226                    clip: clip.to_string(),
227                    weight: *weight,
228                });
229            }
230        }
231        self.play(first, tick)?;
232        let mut sum = 0.0f32;
233        for (index, (clip, weight)) in clips.iter().enumerate() {
234            sum += weight;
235            let share = if sum > 0.0 { weight / sum } else { 0.0 };
236            if index == 0 {
237                self.set_weight(clip, if *weight > 0.0 { 1.0 } else { 0.0 })?;
238            } else {
239                self.blend(clip, share.clamp(0.0, 1.0), tick)?;
240            }
241        }
242        Ok(())
243    }
244
245    pub fn set_playback(&mut self, clip: &str, playback: Playback) -> Result<(), AnimError> {
246        self.layer_mut(clip)?.playback = playback;
247        Ok(())
248    }
249
250    pub fn stop(&mut self) {
251        self.mixes.clear();
252    }
253
254    pub fn is_playing(&self) -> bool {
255        !self.mixes.is_empty()
256    }
257
258    pub fn playing(&self) -> Vec<(&str, f32)> {
259        self.mixes
260            .last()
261            .map(|mix| {
262                mix.layers
263                    .iter()
264                    .map(|layer| (self.rig.clips()[layer.clip].name(), layer.weight))
265                    .collect()
266            })
267            .unwrap_or_default()
268    }
269
270    pub fn time(&self, clip: &str, tick: u64) -> Option<f32> {
271        let index = self.rig.clip(clip)?;
272        let layer = self
273            .mixes
274            .last()?
275            .layers
276            .iter()
277            .find(|layer| layer.clip == index)?;
278        Some(local_time(
279            layer.elapsed(tick, self.rate),
280            self.rig.clips()[index].duration(),
281            layer.playback,
282        ))
283    }
284
285    pub fn finished(&self, clip: &str, tick: u64) -> bool {
286        let Some(index) = self.rig.clip(clip) else {
287            return false;
288        };
289        self.mixes.last().is_some_and(|mix| {
290            mix.layers.iter().any(|layer| {
291                layer.clip == index
292                    && layer.playback == Playback::Once
293                    && layer.elapsed(tick, self.rate) >= self.rig.clips()[index].duration() as f64
294            })
295        })
296    }
297
298    fn mix_pose(&self, mix: &Mix, tick: u64, out: &mut Pose) {
299        out.reset(self.rig.skeleton());
300        for layer in &mix.layers {
301            let clip = &self.rig.clips()[layer.clip];
302            let time = local_time(
303                layer.elapsed(tick, self.rate),
304                clip.duration(),
305                layer.playback,
306            );
307            clip.apply(time, out, layer.weight);
308        }
309    }
310
311    pub fn evaluate(&self, tick: u64, pose: &mut Pose) {
312        let skeleton = self.rig.skeleton();
313        pose.reset(skeleton);
314        let mut layer = Pose::rest(skeleton);
315        let first = self
316            .mixes
317            .iter()
318            .rposition(|mix| mix.progress(tick) >= 1.0)
319            .unwrap_or(0);
320        for (index, mix) in self.mixes.iter().enumerate().skip(first) {
321            if index == first {
322                self.mix_pose(mix, tick, pose);
323            } else {
324                self.mix_pose(mix, tick, &mut layer);
325                pose.mix(&layer, mix.progress(tick));
326            }
327        }
328    }
329
330    pub fn pose(&self, tick: u64) -> Pose {
331        let mut pose = Pose::rest(self.rig.skeleton());
332        self.evaluate(tick, &mut pose);
333        pose
334    }
335
336    pub fn palette(&self, tick: u64) -> Vec<Mat4> {
337        self.pose(tick).palette(self.rig.skeleton())
338    }
339
340    pub fn advance(&mut self, tick: u64) -> Vec<Fired> {
341        if let Some(done) = self.mixes.iter().rposition(|mix| mix.progress(tick) >= 1.0) {
342            self.mixes.drain(..done);
343        }
344        let rate = self.rate;
345        let rig = &self.rig;
346        let mut fired = Vec::new();
347        for mix in &mut self.mixes {
348            for layer in &mut mix.layers {
349                let clip = &rig.clips()[layer.clip];
350                let now = layer.elapsed(tick, rate);
351                let heard = layer.heard.replace(now);
352                if layer.weight <= 0.0 || clip.events().is_empty() {
353                    continue;
354                }
355                let mut found: Vec<(f64, usize)> = Vec::new();
356                for (order, event) in clip.events().iter().enumerate() {
357                    for at in occurrences(
358                        event.time,
359                        clip.duration(),
360                        layer.playback,
361                        heard,
362                        layer.begin,
363                        now,
364                    ) {
365                        found.push((at, order));
366                    }
367                }
368                found.sort_by(|a, b| a.0.total_cmp(&b.0).then(a.1.cmp(&b.1)));
369                fired.extend(found.into_iter().map(|(_, order)| {
370                    let event = &clip.events()[order];
371                    Fired {
372                        clip: clip.name().to_string(),
373                        event: event.name.clone(),
374                        time: event.time,
375                        tick,
376                    }
377                }));
378            }
379        }
380        fired
381    }
382}
383
384fn occurrences(
385    time: f32,
386    duration: f32,
387    playback: Playback,
388    heard: Option<f64>,
389    begin: f64,
390    now: f64,
391) -> Vec<f64> {
392    let time = time as f64;
393    let duration = duration as f64;
394    let lower = heard.unwrap_or(begin);
395    let after = |at: f64| match heard {
396        Some(heard) => at > heard,
397        None => at >= begin,
398    } && at <= now;
399    let series = |base: f64, period: f64| -> Vec<f64> {
400        if now < base {
401            return Vec::new();
402        }
403        let last = ((now - base) / period).floor() as u64;
404        let first = if lower <= base {
405            0
406        } else {
407            ((lower - base) / period).floor() as u64
408        };
409        if first > last {
410            return Vec::new();
411        }
412        let first = first.max(last.saturating_sub(MAX_REPEATS - 1));
413        (first..=last)
414            .map(|k| base + k as f64 * period)
415            .filter(|&at| after(at))
416            .collect()
417    };
418    let mut found = match playback {
419        Playback::Once => return if after(time) { vec![time] } else { Vec::new() },
420        _ if duration <= 0.0 => return if after(time) { vec![time] } else { Vec::new() },
421        Playback::Loop => series(time, duration),
422        Playback::PingPong => {
423            let mut both = series(time, 2.0 * duration);
424            both.extend(series(2.0 * duration - time, 2.0 * duration));
425            both
426        }
427    };
428    found.sort_by(f64::total_cmp);
429    found.dedup();
430    found
431}
432
433fn fade_ticks(seconds: f32, rate: u32) -> Result<u64, AnimError> {
434    if !seconds.is_finite() || seconds < 0.0 {
435        return Err(AnimError::Fade(seconds));
436    }
437    Ok((seconds as f64 * rate as f64).round() as u64)
438}
439
440fn check_weight(clip: &str, weight: f32) -> Result<f32, AnimError> {
441    if !weight.is_finite() || !(0.0..=1.0).contains(&weight) {
442        return Err(AnimError::Weight {
443            clip: clip.to_string(),
444            weight,
445        });
446    }
447    Ok(weight)
448}