1#![allow(dead_code)]
5
6use std::collections::HashMap;
10
11pub type ExprWeights = HashMap<String, f32>;
17
18#[derive(Debug, Clone, PartialEq)]
20pub enum EaseType {
21 Linear,
23 EaseIn,
25 EaseOut,
27 EaseInOut,
29 Step,
31}
32
33pub struct ExprKeyframe {
35 pub time: f32,
37 pub weights: ExprWeights,
39 pub ease_to_next: EaseType,
41 pub hold_duration: f32,
43}
44
45#[derive(Debug, Clone, PartialEq)]
47pub enum SeqLoopMode {
48 Once,
50 Loop,
52 PingPong,
54}
55
56pub struct ExprTrack {
58 pub name: String,
60 pub keyframes: Vec<ExprKeyframe>,
62 pub loop_mode: SeqLoopMode,
64}
65
66pub struct ExprSequencer {
68 tracks: Vec<ExprTrack>,
69}
70
71pub fn ease_value(t: f32, ease: &EaseType) -> f32 {
79 let t = t.clamp(0.0, 1.0);
80 match ease {
81 EaseType::Linear => t,
82 EaseType::EaseIn => t * t,
83 EaseType::EaseOut => 1.0 - (1.0 - t) * (1.0 - t),
84 EaseType::EaseInOut => t * t * (3.0 - 2.0 * t),
85 EaseType::Step => {
86 if t < 1.0 {
87 0.0
88 } else {
89 1.0
90 }
91 }
92 }
93}
94
95pub fn lerp_weights(a: &ExprWeights, b: &ExprWeights, t: f32) -> ExprWeights {
99 let t = t.clamp(0.0, 1.0);
100 let mut out: ExprWeights = HashMap::new();
101
102 for (k, va) in a {
103 let vb = b.get(k).copied().unwrap_or(0.0);
104 out.insert(k.clone(), va + (vb - va) * t);
105 }
106 for (k, vb) in b {
107 if !out.contains_key(k) {
108 out.insert(k.clone(), vb * t);
109 }
110 }
111 out
112}
113
114pub fn blink_track(interval: f32, duration: f32) -> ExprTrack {
119 let interval = interval.max(duration + 0.01);
120 let close_t = duration * 0.3;
121 let open_t = duration;
122
123 let closed: ExprWeights = [("blink".to_string(), 1.0)].into();
124 let open: ExprWeights = [("blink".to_string(), 0.0)].into();
125
126 let mut track = ExprTrack::new("blink");
127 track.loop_mode = SeqLoopMode::Loop;
128
129 track.add_keyframe(ExprKeyframe {
131 time: 0.0,
132 weights: open.clone(),
133 ease_to_next: EaseType::EaseIn,
134 hold_duration: interval - duration,
135 });
136 track.add_keyframe(ExprKeyframe {
137 time: interval - duration,
138 weights: open.clone(),
139 ease_to_next: EaseType::EaseIn,
140 hold_duration: 0.0,
141 });
142 track.add_keyframe(ExprKeyframe {
143 time: interval - duration + close_t,
144 weights: closed,
145 ease_to_next: EaseType::EaseOut,
146 hold_duration: 0.0,
147 });
148 track.add_keyframe(ExprKeyframe {
149 time: interval - duration + open_t,
150 weights: open,
151 ease_to_next: EaseType::Linear,
152 hold_duration: 0.0,
153 });
154
155 track
156}
157
158pub fn breathing_expr_track(rate: f32) -> ExprTrack {
162 let period = 60.0 / rate.max(1.0);
163 let inhale_end = period * 0.4;
164 let exhale_end = period;
165
166 let inhale: ExprWeights = [
167 ("nostrils_flare".to_string(), 0.3),
168 ("chest_expand".to_string(), 0.2),
169 ]
170 .into();
171 let exhale: ExprWeights = [
172 ("nostrils_flare".to_string(), 0.0),
173 ("chest_expand".to_string(), 0.0),
174 ]
175 .into();
176
177 let mut track = ExprTrack::new("breathing");
178 track.loop_mode = SeqLoopMode::Loop;
179
180 track.add_keyframe(ExprKeyframe {
181 time: 0.0,
182 weights: exhale.clone(),
183 ease_to_next: EaseType::EaseInOut,
184 hold_duration: 0.0,
185 });
186 track.add_keyframe(ExprKeyframe {
187 time: inhale_end,
188 weights: inhale,
189 ease_to_next: EaseType::EaseInOut,
190 hold_duration: 0.0,
191 });
192 track.add_keyframe(ExprKeyframe {
193 time: exhale_end,
194 weights: exhale,
195 ease_to_next: EaseType::EaseInOut,
196 hold_duration: 0.0,
197 });
198
199 track
200}
201
202impl ExprTrack {
207 pub fn new(name: &str) -> Self {
209 Self {
210 name: name.to_string(),
211 keyframes: Vec::new(),
212 loop_mode: SeqLoopMode::Once,
213 }
214 }
215
216 pub fn add_keyframe(&mut self, kf: ExprKeyframe) {
218 let pos = self.keyframes.partition_point(|k| k.time <= kf.time);
219 self.keyframes.insert(pos, kf);
220 }
221
222 pub fn remove_keyframe(&mut self, idx: usize) {
224 if idx < self.keyframes.len() {
225 self.keyframes.remove(idx);
226 }
227 }
228
229 pub fn duration(&self) -> f32 {
231 self.keyframes
232 .last()
233 .map(|kf| kf.time + kf.hold_duration)
234 .unwrap_or(0.0)
235 }
236
237 pub fn evaluate(&self, t_in: f32) -> ExprWeights {
239 if self.keyframes.is_empty() {
240 return HashMap::new();
241 }
242 if self.keyframes.len() == 1 {
243 return self.keyframes[0].weights.clone();
244 }
245
246 let dur = self.duration();
247 let t = self.remap_time(t_in, dur);
248
249 let next_idx = self.keyframes.partition_point(|kf| kf.time <= t);
251
252 if next_idx == 0 {
253 return self.keyframes[0].weights.clone();
254 }
255 if next_idx >= self.keyframes.len() {
256 return self.keyframes[self.keyframes.len() - 1].weights.clone();
257 }
258
259 let prev = &self.keyframes[next_idx - 1];
260 let next = &self.keyframes[next_idx];
261
262 let transition_start = prev.time + prev.hold_duration;
264
265 if t <= transition_start {
267 return prev.weights.clone();
268 }
269
270 let span = next.time - transition_start;
271 let local_t = if span > f32::EPSILON {
272 ((t - transition_start) / span).clamp(0.0, 1.0)
273 } else {
274 1.0
275 };
276
277 let eased_t = ease_value(local_t, &prev.ease_to_next);
278 lerp_weights(&prev.weights, &next.weights, eased_t)
279 }
280
281 fn remap_time(&self, t: f32, dur: f32) -> f32 {
286 if dur <= 0.0 {
287 return 0.0;
288 }
289 match self.loop_mode {
290 SeqLoopMode::Once => t.clamp(0.0, dur),
291 SeqLoopMode::Loop => {
292 let wrapped = t % dur;
293 if wrapped < 0.0 {
294 wrapped + dur
295 } else {
296 wrapped
297 }
298 }
299 SeqLoopMode::PingPong => {
300 let cycle = dur * 2.0;
301 let pos = t % cycle;
302 let pos = if pos < 0.0 { pos + cycle } else { pos };
303 if pos <= dur {
304 pos
305 } else {
306 cycle - pos
307 }
308 }
309 }
310 }
311}
312
313impl ExprSequencer {
318 pub fn new() -> Self {
320 Self { tracks: Vec::new() }
321 }
322
323 pub fn add_track(&mut self, track: ExprTrack) {
325 self.tracks.push(track);
326 }
327
328 pub fn track_count(&self) -> usize {
330 self.tracks.len()
331 }
332
333 pub fn evaluate_all(&self, t: f32) -> ExprWeights {
338 let mut out: ExprWeights = HashMap::new();
339 for track in &self.tracks {
340 let w = track.evaluate(t);
341 for (k, v) in w {
342 *out.entry(k).or_insert(0.0) += v;
343 }
344 }
345 for v in out.values_mut() {
347 *v = v.clamp(0.0, 1.0);
348 }
349 out
350 }
351}
352
353impl Default for ExprSequencer {
354 fn default() -> Self {
355 Self::new()
356 }
357}
358
359#[cfg(test)]
364mod tests {
365 use super::*;
366
367 fn approx(a: f32, b: f32) -> bool {
368 (a - b).abs() < 1e-5
369 }
370
371 fn single_weight(name: &str, v: f32) -> ExprWeights {
372 [(name.to_string(), v)].into()
373 }
374
375 #[test]
378 fn test_ease_linear() {
379 assert!(approx(ease_value(0.0, &EaseType::Linear), 0.0));
380 assert!(approx(ease_value(0.5, &EaseType::Linear), 0.5));
381 assert!(approx(ease_value(1.0, &EaseType::Linear), 1.0));
382 }
383
384 #[test]
385 fn test_ease_in() {
386 assert!(approx(ease_value(0.0, &EaseType::EaseIn), 0.0));
387 assert!(approx(ease_value(0.5, &EaseType::EaseIn), 0.25));
388 assert!(approx(ease_value(1.0, &EaseType::EaseIn), 1.0));
389 }
390
391 #[test]
392 fn test_ease_out() {
393 assert!(approx(ease_value(0.0, &EaseType::EaseOut), 0.0));
394 assert!(approx(ease_value(0.5, &EaseType::EaseOut), 0.75));
395 assert!(approx(ease_value(1.0, &EaseType::EaseOut), 1.0));
396 }
397
398 #[test]
399 fn test_ease_in_out_midpoint() {
400 assert!(approx(ease_value(0.5, &EaseType::EaseInOut), 0.5));
402 }
403
404 #[test]
405 fn test_ease_step() {
406 assert!(approx(ease_value(0.0, &EaseType::Step), 0.0));
407 assert!(approx(ease_value(0.5, &EaseType::Step), 0.0));
408 assert!(approx(ease_value(1.0, &EaseType::Step), 1.0));
409 }
410
411 #[test]
412 fn test_ease_clamps_input() {
413 assert!(approx(ease_value(-1.0, &EaseType::Linear), 0.0));
414 assert!(approx(ease_value(2.0, &EaseType::Linear), 1.0));
415 }
416
417 #[test]
420 fn test_lerp_weights_midpoint() {
421 let a = single_weight("smile", 0.0);
422 let b = single_weight("smile", 1.0);
423 let mid = lerp_weights(&a, &b, 0.5);
424 assert!(approx(*mid.get("smile").expect("should succeed"), 0.5));
425 }
426
427 #[test]
428 fn test_lerp_weights_missing_key_in_b() {
429 let a = single_weight("anger", 0.8);
430 let b: ExprWeights = HashMap::new();
431 let result = lerp_weights(&a, &b, 0.5);
432 assert!(approx(*result.get("anger").expect("should succeed"), 0.4));
433 }
434
435 #[test]
436 fn test_lerp_weights_missing_key_in_a() {
437 let a: ExprWeights = HashMap::new();
438 let b = single_weight("joy", 1.0);
439 let result = lerp_weights(&a, &b, 0.5);
440 assert!(approx(*result.get("joy").expect("should succeed"), 0.5));
441 }
442
443 #[test]
444 fn test_lerp_weights_t0_equals_a() {
445 let a = single_weight("fear", 0.6);
446 let b = single_weight("fear", 0.0);
447 let result = lerp_weights(&a, &b, 0.0);
448 assert!(approx(*result.get("fear").expect("should succeed"), 0.6));
449 }
450
451 #[test]
454 fn test_track_add_sorted() {
455 let mut track = ExprTrack::new("test");
456 track.add_keyframe(ExprKeyframe {
457 time: 2.0,
458 weights: single_weight("a", 1.0),
459 ease_to_next: EaseType::Linear,
460 hold_duration: 0.0,
461 });
462 track.add_keyframe(ExprKeyframe {
463 time: 0.0,
464 weights: single_weight("a", 0.0),
465 ease_to_next: EaseType::Linear,
466 hold_duration: 0.0,
467 });
468 assert!(approx(track.keyframes[0].time, 0.0));
469 assert!(approx(track.keyframes[1].time, 2.0));
470 }
471
472 #[test]
473 fn test_track_duration() {
474 let mut track = ExprTrack::new("dur_test");
475 track.add_keyframe(ExprKeyframe {
476 time: 3.0,
477 weights: HashMap::new(),
478 ease_to_next: EaseType::Linear,
479 hold_duration: 0.5,
480 });
481 assert!(approx(track.duration(), 3.5));
482 }
483
484 #[test]
485 fn test_track_remove_keyframe() {
486 let mut track = ExprTrack::new("rm");
487 track.add_keyframe(ExprKeyframe {
488 time: 0.0,
489 weights: HashMap::new(),
490 ease_to_next: EaseType::Linear,
491 hold_duration: 0.0,
492 });
493 track.add_keyframe(ExprKeyframe {
494 time: 1.0,
495 weights: HashMap::new(),
496 ease_to_next: EaseType::Linear,
497 hold_duration: 0.0,
498 });
499 track.remove_keyframe(0);
500 assert_eq!(track.keyframes.len(), 1);
501 assert!(approx(track.keyframes[0].time, 1.0));
502 }
503
504 #[test]
505 fn test_track_evaluate_at_start() {
506 let mut track = ExprTrack::new("eval");
507 track.add_keyframe(ExprKeyframe {
508 time: 0.0,
509 weights: single_weight("smile", 0.0),
510 ease_to_next: EaseType::Linear,
511 hold_duration: 0.0,
512 });
513 track.add_keyframe(ExprKeyframe {
514 time: 1.0,
515 weights: single_weight("smile", 1.0),
516 ease_to_next: EaseType::Linear,
517 hold_duration: 0.0,
518 });
519 let w = track.evaluate(0.0);
520 assert!(approx(*w.get("smile").expect("should succeed"), 0.0));
521 }
522
523 #[test]
524 fn test_track_evaluate_at_end() {
525 let mut track = ExprTrack::new("eval_end");
526 track.add_keyframe(ExprKeyframe {
527 time: 0.0,
528 weights: single_weight("smile", 0.0),
529 ease_to_next: EaseType::Linear,
530 hold_duration: 0.0,
531 });
532 track.add_keyframe(ExprKeyframe {
533 time: 1.0,
534 weights: single_weight("smile", 1.0),
535 ease_to_next: EaseType::Linear,
536 hold_duration: 0.0,
537 });
538 let w = track.evaluate(1.0);
539 assert!(approx(*w.get("smile").expect("should succeed"), 1.0));
540 }
541
542 #[test]
543 fn test_track_evaluate_midpoint_linear() {
544 let mut track = ExprTrack::new("mid_linear");
545 track.add_keyframe(ExprKeyframe {
546 time: 0.0,
547 weights: single_weight("brow", 0.0),
548 ease_to_next: EaseType::Linear,
549 hold_duration: 0.0,
550 });
551 track.add_keyframe(ExprKeyframe {
552 time: 2.0,
553 weights: single_weight("brow", 1.0),
554 ease_to_next: EaseType::Linear,
555 hold_duration: 0.0,
556 });
557 let w = track.evaluate(1.0);
558 assert!(approx(*w.get("brow").expect("should succeed"), 0.5));
559 }
560
561 #[test]
562 fn test_track_loop_mode() {
563 let mut track = ExprTrack::new("loop");
564 track.loop_mode = SeqLoopMode::Loop;
565 track.add_keyframe(ExprKeyframe {
566 time: 0.0,
567 weights: single_weight("x", 0.0),
568 ease_to_next: EaseType::Linear,
569 hold_duration: 0.0,
570 });
571 track.add_keyframe(ExprKeyframe {
572 time: 1.0,
573 weights: single_weight("x", 1.0),
574 ease_to_next: EaseType::Linear,
575 hold_duration: 0.0,
576 });
577 let w = track.evaluate(1.5);
579 assert!(approx(*w.get("x").expect("should succeed"), 0.5));
580 }
581
582 #[test]
583 fn test_track_pingpong_mode() {
584 let mut track = ExprTrack::new("pp");
585 track.loop_mode = SeqLoopMode::PingPong;
586 track.add_keyframe(ExprKeyframe {
587 time: 0.0,
588 weights: single_weight("y", 0.0),
589 ease_to_next: EaseType::Linear,
590 hold_duration: 0.0,
591 });
592 track.add_keyframe(ExprKeyframe {
593 time: 1.0,
594 weights: single_weight("y", 1.0),
595 ease_to_next: EaseType::Linear,
596 hold_duration: 0.0,
597 });
598 let w = track.evaluate(1.5);
600 assert!(approx(*w.get("y").expect("should succeed"), 0.5));
601 }
602
603 #[test]
604 fn test_track_hold_duration() {
605 let mut track = ExprTrack::new("hold");
606 track.add_keyframe(ExprKeyframe {
607 time: 0.0,
608 weights: single_weight("sad", 1.0),
609 ease_to_next: EaseType::Linear,
610 hold_duration: 1.0, });
612 track.add_keyframe(ExprKeyframe {
613 time: 2.0,
614 weights: single_weight("sad", 0.0),
615 ease_to_next: EaseType::Linear,
616 hold_duration: 0.0,
617 });
618 let w = track.evaluate(0.5);
620 assert!(approx(*w.get("sad").expect("should succeed"), 1.0));
621 let w2 = track.evaluate(1.5);
623 assert!(approx(*w2.get("sad").expect("should succeed"), 0.5));
624 }
625
626 #[test]
629 fn test_sequencer_track_count() {
630 let mut seq = ExprSequencer::new();
631 seq.add_track(ExprTrack::new("a"));
632 seq.add_track(ExprTrack::new("b"));
633 assert_eq!(seq.track_count(), 2);
634 }
635
636 #[test]
637 fn test_sequencer_evaluate_all_additive_clamp() {
638 let mut seq = ExprSequencer::new();
639
640 let mut t1 = ExprTrack::new("t1");
641 t1.add_keyframe(ExprKeyframe {
642 time: 0.0,
643 weights: single_weight("smile", 0.8),
644 ease_to_next: EaseType::Linear,
645 hold_duration: 0.0,
646 });
647
648 let mut t2 = ExprTrack::new("t2");
649 t2.add_keyframe(ExprKeyframe {
650 time: 0.0,
651 weights: single_weight("smile", 0.6),
652 ease_to_next: EaseType::Linear,
653 hold_duration: 0.0,
654 });
655
656 seq.add_track(t1);
657 seq.add_track(t2);
658
659 let w = seq.evaluate_all(0.0);
660 assert!(approx(*w.get("smile").expect("should succeed"), 1.0));
662 }
663
664 #[test]
667 fn test_blink_track_loops() {
668 let track = blink_track(4.0, 0.2);
669 assert_eq!(track.loop_mode, SeqLoopMode::Loop);
670 }
671
672 #[test]
673 fn test_blink_track_has_keyframes() {
674 let track = blink_track(4.0, 0.2);
675 assert!(track.keyframes.len() >= 3);
676 }
677
678 #[test]
681 fn test_breathing_track_loops() {
682 let track = breathing_expr_track(15.0);
683 assert_eq!(track.loop_mode, SeqLoopMode::Loop);
684 }
685
686 #[test]
687 fn test_breathing_track_has_keyframes() {
688 let track = breathing_expr_track(15.0);
689 assert!(track.keyframes.len() >= 2);
690 }
691
692 #[test]
693 fn test_breathing_track_period() {
694 let rate = 12.0_f32;
695 let expected_period = 60.0 / rate;
696 let track = breathing_expr_track(rate);
697 assert!(approx(track.duration(), expected_period));
698 }
699}