1use crate::scheduler::{seconds, ticks};
11use banished_formats::skeleton::Transform;
12use banished_formats::types::AnimationClip;
13
14#[derive(Debug, Clone, PartialEq)]
17pub struct Playback {
18 pub clip: usize,
20 pub elapsed: i64,
22 pub sample_time: i64,
24 pub sample_delta: i64,
26 pub speed: f32,
28}
29
30impl Playback {
31 pub fn new(clip: usize, start: i64) -> Self {
35 Self {
36 clip,
37 elapsed: start,
38 sample_time: start,
39 sample_delta: 0,
40 speed: 1.0,
41 }
42 }
43
44 pub fn is_animating(&self, clip: &AnimationClip) -> bool {
49 clip.looping || self.elapsed < clip.duration_ticks
50 }
51
52 pub fn update(&mut self, clip: &AnimationClip, dt: i64) -> Vec<usize> {
55 self.sample_time = self.elapsed;
56 self.sample_delta = ticks(seconds(dt) * self.speed);
57 let delta = if self.speed == 1.0 {
58 dt
59 } else {
60 self.sample_delta
61 };
62 let next = self.elapsed + delta;
63 let events = clip
64 .events
65 .iter()
66 .enumerate()
67 .filter_map(|(index, event)| {
68 let time = ticks(
69 (event.frame - clip.start_frame) as f32
70 / (clip.end_frame - clip.start_frame) as f32
71 * clip.frames.len() as f32
72 * clip.sample_interval,
73 );
74 let crossed = self.elapsed <= time && time < next;
75 let wrapped = next >= clip.duration_ticks
76 && self.elapsed - clip.duration_ticks <= time
77 && time < next - clip.duration_ticks;
78 (crossed || wrapped).then_some(index)
79 })
80 .collect();
81 self.elapsed = next;
82 if !clip.looping {
83 if self.elapsed >= clip.duration_ticks {
84 self.elapsed = clip.duration_ticks;
85 }
86 } else if clip.duration_ticks > 0 && self.elapsed >= clip.duration_ticks {
87 self.elapsed %= clip.duration_ticks;
89 let loop_seconds = if clip.loop_start_frame == 0 {
90 0.0
91 } else {
92 (clip.loop_start_frame - clip.start_frame) as f32
93 / (clip.end_frame - clip.start_frame) as f32
94 * clip.frames.len() as f32
95 * clip.sample_interval
96 };
97 self.elapsed += ticks(loop_seconds);
98 }
99 events
100 }
101
102 pub fn pose(&self, clip: &AnimationClip) -> Vec<Transform> {
104 sample_pose(clip, self.sample_time, self.sample_delta)
105 }
106}
107
108fn quaternion_lerp(a: [f32; 4], b: [f32; 4], fraction: f32) -> [f32; 4] {
111 let native = |mut q: [f32; 4]| {
114 for value in &mut q[..3] {
115 *value = -*value;
116 }
117 q
118 };
119 let a = native(a);
120 let b = native(b);
121 let old = 1.0 - fraction;
122 let dot = a[0] * b[0] + a[1] * b[1] + a[2] * b[2] + a[3] * b[3];
123 let new = if dot <= 0.0 { -fraction } else { fraction };
124 let q = std::array::from_fn::<_, 4, _>(|i| a[i] * old + b[i] * new);
125 let length = (q[1] * q[1] + q[0] * q[0] + q[2] * q[2] + q[3] * q[3]).sqrt();
126 let inverse = 1.0 / length;
127 native(q.map(|v| v * inverse))
128}
129
130pub fn sample_pose(clip: &AnimationClip, time: i64, delta: i64) -> Vec<Transform> {
136 let count = clip.frames.len();
137 if count == 0 {
138 return Vec::new();
139 }
140 let position = seconds(time) * clip.sample_rate;
141 let raw = position as i32;
142 let loop_start = if clip.loop_start_frame == 0 {
143 0
144 } else {
145 ((clip.loop_start_frame - clip.start_frame) as f32
146 / (clip.end_frame - clip.start_frame) as f32
147 * count as f32) as i32
148 };
149 let span = (count as i32 - loop_start).max(1);
151 let first = if raw < count as i32 {
152 raw.max(0)
153 } else if clip.looping {
154 loop_start + raw % span
155 } else {
156 count as i32 - 1
157 } as usize;
158 let second = if first + 1 < count {
159 first + 1
160 } else if clip.looping {
161 (first as i32 + 1) as usize % span as usize + loop_start as usize
162 } else {
163 count - 1
164 };
165 let fraction = position - raw as f32;
166 let mut pose = if first == second || fraction == 0.0 {
167 clip.frames[first].clone()
168 } else {
169 clip.frames[first]
170 .iter()
171 .zip(&clip.frames[second])
172 .map(|(a, b)| Transform {
173 rotation: quaternion_lerp(a.rotation, b.rotation, fraction),
174 translation: std::array::from_fn(|i| {
175 a.translation[i] + (b.translation[i] - a.translation[i]) * fraction
176 }),
177 })
178 .collect()
179 };
180 if let Some(root) = pose.first_mut() {
181 root.rotation = Transform::IDENTITY.rotation;
182 let scale = seconds(delta) * clip.sample_rate;
183 root.translation = root.translation.map(|v| v * scale);
184 }
185 pose
186}
187
188#[derive(Debug, Clone, PartialEq)]
192pub struct Layer {
193 pub playback: Playback,
195 pub blend: i64,
197 pub left: i64,
199 pub started: bool,
202 pub instance: Option<i32>,
205}
206
207#[derive(Debug, Clone, PartialEq, Default)]
210pub struct AnimationStack {
211 pub layers: Vec<Layer>,
213 pose: Vec<Transform>,
215}
216
217fn blend_seconds(group: &banished_formats::types::AnimationGroup, old: usize, new: usize) -> f32 {
220 let n = group.clips.len();
221 group
222 .blend_matrix
223 .get(n * new + old)
224 .copied()
225 .unwrap_or(group.default_blend)
226}
227
228impl AnimationStack {
229 pub fn new(clip: usize, start: i64) -> Self {
231 Self {
232 layers: vec![Layer {
233 playback: Playback::new(clip, start),
234 blend: 0,
235 left: 0,
236 started: false,
237 instance: None,
238 }],
239 pose: Vec::new(),
240 }
241 }
242
243 pub fn clip(&self) -> Option<usize> {
245 self.layers.first().map(|l| l.playback.clip)
246 }
247
248 pub fn play(
255 &mut self,
256 group: &banished_formats::types::AnimationGroup,
257 clip: usize,
258 start: i64,
259 ) {
260 if let Some(top) = self.layers.first_mut() {
261 if top.playback.clip == clip && group.clips.get(clip).is_some_and(|c| c.looping) {
262 top.playback.speed = 1.0;
263 return;
264 }
265 }
266 let instance = group
269 .clips
270 .get(clip)
271 .filter(|c| c.instanced && c.instance_count > 0)
272 .map(|_| shared::instance_id(group, clip));
273 let layer = Layer {
274 playback: Playback::new(clip, start),
275 blend: 0,
276 left: 0,
277 started: false,
278 instance,
279 };
280 if self.layers.is_empty() {
281 self.layers.push(layer);
282 return;
283 }
284 if !self.layers[0].started && self.layers.len() != 1 {
285 let t = ticks(blend_seconds(group, self.layers[1].playback.clip, clip));
286 self.layers[1].blend = t;
287 self.layers[1].left = t;
288 self.layers[0] = layer;
289 return;
290 }
291 let t = ticks(blend_seconds(group, self.layers[0].playback.clip, clip));
292 self.layers[0].blend = t;
293 self.layers[0].left = t;
294 self.layers.insert(0, layer);
295 }
296
297 pub fn update(
305 &mut self,
306 group: &banished_formats::types::AnimationGroup,
307 dt: i64,
308 ) -> Vec<usize> {
309 let Some(top) = self.layers.first_mut() else {
310 return Vec::new();
311 };
312 top.started = true;
313 let weights: Vec<f32> = self
314 .layers
315 .iter()
316 .map(|l| {
317 if l.blend > 0 {
318 seconds(l.left) / seconds(l.blend)
319 } else {
320 0.0
321 }
322 })
323 .collect();
324 let mut events = Vec::new();
325 for (i, layer) in self.layers.iter_mut().enumerate() {
326 if let Some(clip) = group.clips.get(layer.playback.clip) {
327 let fired = layer.playback.update(clip, dt);
328 if i == 0 {
329 events = fired;
330 }
331 }
332 }
333 let poses: Vec<Option<Vec<Transform>>> = self
336 .layers
337 .iter()
338 .map(|l| match l.instance {
339 Some(id) => shared::pose(id),
340 None => group.clips.get(l.playback.clip).map(|c| l.playback.pose(c)),
341 })
342 .collect();
343 let mut pose: Option<Vec<Transform>> = None;
344 for i in (0..self.layers.len()).rev() {
345 let Some(new) = &poses[i] else { continue };
346 pose = Some(match pose {
347 None => new.clone(),
348 Some(old) => {
349 let w = weights.get(i + 1).copied().unwrap_or(0.0);
350 new.iter()
351 .zip(&old)
352 .map(|(n, o)| Transform {
353 rotation: quaternion_lerp(n.rotation, o.rotation, w),
354 translation: std::array::from_fn(|k| {
355 n.translation[k] + (o.translation[k] - n.translation[k]) * w
356 }),
357 })
358 .collect()
359 }
360 });
361 }
362 self.pose = pose.unwrap_or_default();
363 let step = ticks(seconds(dt));
364 let mut i = 0;
365 while i < self.layers.len() {
366 let l = &mut self.layers[i];
367 if l.blend > 0 {
368 l.left -= step;
369 if l.left < 1 {
370 if let Some(id) = l.instance {
371 shared::free(id);
372 }
373 self.layers.remove(i);
374 continue;
375 }
376 }
377 i += 1;
378 }
379 events
380 }
381
382 pub fn pose(&self, group: &banished_formats::types::AnimationGroup) -> Option<Vec<Transform>> {
384 if !self.pose.is_empty() {
385 return Some(self.pose.clone());
386 }
387 let top = self.layers.first()?;
388 group
389 .clips
390 .get(top.playback.clip)
391 .map(|c| top.playback.pose(c))
392 }
393}
394
395pub mod shared {
412 use super::Playback;
413 use banished_formats::skeleton::Transform;
414 use banished_formats::types::{AnimationClip, AnimationGroup};
415 use std::cell::RefCell;
416
417 struct Entry {
418 group: usize,
419 clip: usize,
420 users: i32,
421 data: AnimationClip,
422 instances: Vec<(Playback, Vec<Transform>)>,
423 }
424
425 thread_local! {
426 static ENTRIES: RefCell<Vec<Option<Entry>>> = const { RefCell::new(Vec::new()) };
427 }
428
429 pub fn reset() {
431 ENTRIES.with(|e| e.borrow_mut().clear());
432 }
433
434 #[derive(Default)]
437 pub struct Table(Vec<Option<Entry>>);
438
439 pub fn swap(table: &mut Table) {
441 ENTRIES.with(|e| std::mem::swap(&mut *e.borrow_mut(), &mut table.0));
442 }
443
444 pub fn instance_id(group: &AnimationGroup, clip: usize) -> i32 {
446 let key = group as *const AnimationGroup as usize;
447 ENTRIES.with(|e| {
448 let mut entries = e.borrow_mut();
449 let found = entries
450 .iter()
451 .position(|x| x.as_ref().is_some_and(|x| x.group == key && x.clip == clip));
452 let index = match found {
453 Some(i) => i,
454 None => {
455 let data = group.clips[clip].clone();
456 let count = data.instance_count.max(1) as usize;
457 let step = super::seconds(data.duration_ticks) * (1.0 / count as f32);
458 let mut at = 0.0f32;
459 let mut instances = Vec::with_capacity(count);
460 for _ in 0..count {
461 let mut p = Playback::new(clip, super::ticks(at));
462 for _ in 0..2 {
463 p.update(&data, 0);
464 }
465 let pose = p.pose(&data);
466 instances.push((p, pose));
467 at += step;
468 }
469 let entry = Entry {
470 group: key,
471 clip,
472 users: 0,
473 data,
474 instances,
475 };
476 match entries.iter().position(Option::is_none) {
477 Some(free) => {
478 entries[free] = Some(entry);
479 free
480 }
481 None => {
482 entries.push(Some(entry));
483 entries.len() - 1
484 }
485 }
486 }
487 };
488 let entry = entries[index].as_mut().unwrap();
489 entry.users += 1;
490 let mut best = (f32::MAX, 0usize);
491 for (k, (p, _)) in entry.instances.iter().enumerate() {
492 let t = super::seconds(p.elapsed);
493 if t < best.0 {
494 best = (t, k);
495 }
496 }
497 ((index as i32) << 8) | best.1 as i32
498 })
499 }
500
501 pub fn free(id: i32) {
503 ENTRIES.with(|e| {
504 let mut entries = e.borrow_mut();
505 let i = (id >> 8 & 0xff) as usize;
506 if let Some(Some(entry)) = entries.get_mut(i) {
507 entry.users -= 1;
508 if entry.users == 0 {
509 entries[i] = None;
510 }
511 }
512 });
513 }
514
515 pub fn update(dt: i64) {
517 ENTRIES.with(|e| {
518 for entry in e.borrow_mut().iter_mut().flatten() {
519 for (p, pose) in &mut entry.instances {
520 p.update(&entry.data, dt);
521 *pose = p.pose(&entry.data);
522 }
523 }
524 });
525 }
526
527 pub fn pose(id: i32) -> Option<Vec<Transform>> {
529 ENTRIES.with(|e| {
530 let entries = e.borrow();
531 let entry = entries.get((id >> 8 & 0xff) as usize)?.as_ref()?;
532 entry
533 .instances
534 .get((id & 0xff) as usize)
535 .map(|(_, pose)| pose.clone())
536 })
537 }
538}