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}