1pub mod ik;
27
28use std::collections::HashMap;
29use crate::math::MathFunction;
30
31#[derive(Debug, Clone)]
35pub enum AnimationCurve {
36 Constant(f32),
38 Keyframes(Vec<(f32, f32)>),
40 MathDriven(MathFunction),
42 BezierKeyframes(Vec<BezierKey>),
44}
45
46#[derive(Debug, Clone)]
47pub struct BezierKey {
48 pub time: f32,
49 pub value: f32,
50 pub in_tangent: f32,
51 pub out_tangent: f32,
52}
53
54impl AnimationCurve {
55 pub fn evaluate(&self, t: f32) -> f32 {
57 let t = t.clamp(0.0, 1.0);
58 match self {
59 AnimationCurve::Constant(v) => *v,
60 AnimationCurve::MathDriven(f) => f.evaluate(t, t),
61 AnimationCurve::Keyframes(keys) => {
62 if keys.is_empty() { return 0.0; }
63 if keys.len() == 1 { return keys[0].1; }
64 let idx = keys.partition_point(|(kt, _)| *kt <= t);
66 if idx == 0 { return keys[0].1; }
67 if idx >= keys.len() { return keys[keys.len()-1].1; }
68 let (t0, v0) = keys[idx-1];
69 let (t1, v1) = keys[idx];
70 let span = (t1 - t0).max(1e-7);
71 let alpha = (t - t0) / span;
72 v0 + (v1 - v0) * alpha
73 }
74 AnimationCurve::BezierKeyframes(keys) => {
75 if keys.is_empty() { return 0.0; }
76 if keys.len() == 1 { return keys[0].value; }
77 let idx = keys.partition_point(|k| k.time <= t);
78 if idx == 0 { return keys[0].value; }
79 if idx >= keys.len() { return keys[keys.len()-1].value; }
80 let k0 = &keys[idx-1];
81 let k1 = &keys[idx];
82 let span = (k1.time - k0.time).max(1e-7);
83 let u = (t - k0.time) / span;
84 let h00 = 2.0*u*u*u - 3.0*u*u + 1.0;
86 let h10 = u*u*u - 2.0*u*u + u;
87 let h01 = -2.0*u*u*u + 3.0*u*u;
88 let h11 = u*u*u - u*u;
89 h00*k0.value + h10*span*k0.out_tangent + h01*k1.value + h11*span*k1.in_tangent
90 }
91 }
92 }
93
94 pub fn constant(v: f32) -> Self { Self::Constant(v) }
96
97 pub fn linear(a: f32, b: f32) -> Self {
99 Self::Keyframes(vec![(0.0, a), (1.0, b)])
100 }
101
102 pub fn from_keys(mut keys: Vec<(f32, f32)>) -> Self {
104 keys.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap());
105 Self::Keyframes(keys)
106 }
107}
108
109#[derive(Debug, Clone)]
113pub struct AnimationClip {
114 pub name: String,
115 pub duration: f32,
116 pub looping: bool,
117 pub channels: HashMap<String, AnimationCurve>,
119 pub events: Vec<AnimationEvent>,
121}
122
123#[derive(Debug, Clone)]
124pub struct AnimationEvent {
125 pub time: f32,
127 pub tag: String,
129 pub payload: f32,
130}
131
132impl AnimationClip {
133 pub fn new(name: impl Into<String>, duration: f32, looping: bool) -> Self {
134 Self {
135 name: name.into(),
136 duration,
137 looping,
138 channels: HashMap::new(),
139 events: Vec::new(),
140 }
141 }
142
143 pub fn with_channel(mut self, path: impl Into<String>, curve: AnimationCurve) -> Self {
145 self.channels.insert(path.into(), curve);
146 self
147 }
148
149 pub fn with_event(mut self, t: f32, tag: impl Into<String>, payload: f32) -> Self {
151 self.events.push(AnimationEvent { time: t, tag: tag.into(), payload });
152 self
153 }
154
155 pub fn sample(&self, t: f32) -> Vec<(String, f32)> {
157 self.channels.iter()
158 .map(|(path, curve)| (path.clone(), curve.evaluate(t)))
159 .collect()
160 }
161}
162
163#[derive(Debug, Clone)]
167pub enum BlendTreeKind {
168 Linear1D { param: String, thresholds: Vec<f32> },
170 Cartesian2D { param_x: String, param_y: String, positions: Vec<(f32, f32)> },
172 Additive { base_index: usize },
174 Override,
176}
177
178#[derive(Debug, Clone)]
179pub struct BlendTree {
180 pub kind: BlendTreeKind,
181 pub clips: Vec<AnimationClip>,
182 pub weights: Vec<f32>,
183}
184
185impl BlendTree {
186 pub fn compute_weights(&mut self, params: &HashMap<String, ParamValue>) {
188 let n = self.clips.len();
189 if n == 0 { return; }
190 self.weights.resize(n, 0.0);
191
192 match &self.kind {
193 BlendTreeKind::Linear1D { param, thresholds } => {
194 let v = params.get(param).and_then(|p| p.as_float()).unwrap_or(0.0);
195 let thresholds = thresholds.clone();
196 if thresholds.len() != n { return; }
197 let idx = thresholds.partition_point(|&t| t <= v);
199 for w in &mut self.weights { *w = 0.0; }
200 if idx == 0 {
201 self.weights[0] = 1.0;
202 } else if idx >= n {
203 self.weights[n-1] = 1.0;
204 } else {
205 let t0 = thresholds[idx-1];
206 let t1 = thresholds[idx];
207 let span = (t1 - t0).max(1e-7);
208 let alpha = (v - t0) / span;
209 self.weights[idx-1] = 1.0 - alpha;
210 self.weights[idx] = alpha;
211 }
212 }
213 BlendTreeKind::Cartesian2D { param_x, param_y, positions } => {
214 let px = params.get(param_x).and_then(|p| p.as_float()).unwrap_or(0.0);
215 let py = params.get(param_y).and_then(|p| p.as_float()).unwrap_or(0.0);
216 let positions = positions.clone();
217 let dists: Vec<f32> = positions.iter()
219 .map(|(x, y)| ((px - x).powi(2) + (py - y).powi(2)).sqrt().max(1e-6))
220 .collect();
221 let sum: f32 = dists.iter().map(|d| 1.0 / d).sum();
222 for (i, d) in dists.iter().enumerate() {
223 self.weights[i] = (1.0 / d) / sum.max(1e-7);
224 }
225 }
226 BlendTreeKind::Additive { .. } | BlendTreeKind::Override => {
227 }
229 }
230 }
231
232 pub fn sample(&self, t: f32) -> Vec<(String, f32)> {
234 if self.clips.is_empty() { return Vec::new(); }
235 let mut accum: HashMap<String, f32> = HashMap::new();
236 let mut total_weight = 0.0_f32;
237
238 for (clip, &w) in self.clips.iter().zip(self.weights.iter()) {
239 if w < 1e-6 { continue; }
240 total_weight += w;
241 for (path, val) in clip.sample(t) {
242 *accum.entry(path).or_insert(0.0) += val * w;
243 }
244 }
245
246 if total_weight > 1e-6 {
247 for v in accum.values_mut() { *v /= total_weight; }
248 }
249 accum.into_iter().collect()
250 }
251}
252
253#[derive(Debug, Clone)]
257pub enum Condition {
258 FloatGt { param: String, threshold: f32 },
259 FloatLt { param: String, threshold: f32 },
260 FloatGe { param: String, threshold: f32 },
261 FloatLe { param: String, threshold: f32 },
262 FloatEq { param: String, value: f32, tolerance: f32 },
263 BoolTrue { param: String },
264 BoolFalse { param: String },
265 Trigger { param: String },
267 Always,
269 All(Vec<Condition>),
271 Any(Vec<Condition>),
273}
274
275impl Condition {
276 pub fn float_gt(param: impl Into<String>, v: f32) -> Self {
277 Self::FloatGt { param: param.into(), threshold: v }
278 }
279 pub fn float_lt(param: impl Into<String>, v: f32) -> Self {
280 Self::FloatLt { param: param.into(), threshold: v }
281 }
282 pub fn float_ge(param: impl Into<String>, v: f32) -> Self {
283 Self::FloatGe { param: param.into(), threshold: v }
284 }
285 pub fn float_le(param: impl Into<String>, v: f32) -> Self {
286 Self::FloatLe { param: param.into(), threshold: v }
287 }
288 pub fn bool_true(param: impl Into<String>) -> Self {
289 Self::BoolTrue { param: param.into() }
290 }
291 pub fn trigger(param: impl Into<String>) -> Self {
292 Self::Trigger { param: param.into() }
293 }
294
295 pub fn evaluate(&self, params: &HashMap<String, ParamValue>) -> (bool, Vec<String>) {
297 match self {
298 Self::FloatGt { param, threshold } =>
299 (params.get(param).and_then(|p| p.as_float()).unwrap_or(0.0) > *threshold, vec![]),
300 Self::FloatLt { param, threshold } =>
301 (params.get(param).and_then(|p| p.as_float()).unwrap_or(0.0) < *threshold, vec![]),
302 Self::FloatGe { param, threshold } =>
303 (params.get(param).and_then(|p| p.as_float()).unwrap_or(0.0) >= *threshold, vec![]),
304 Self::FloatLe { param, threshold } =>
305 (params.get(param).and_then(|p| p.as_float()).unwrap_or(0.0) <= *threshold, vec![]),
306 Self::FloatEq { param, value, tolerance } => {
307 let v = params.get(param).and_then(|p| p.as_float()).unwrap_or(0.0);
308 ((v - value).abs() <= *tolerance, vec![])
309 }
310 Self::BoolTrue { param } =>
311 (params.get(param).and_then(|p| p.as_bool()).unwrap_or(false), vec![]),
312 Self::BoolFalse { param } =>
313 (!params.get(param).and_then(|p| p.as_bool()).unwrap_or(false), vec![]),
314 Self::Trigger { param } => {
315 let v = params.get(param).and_then(|p| p.as_bool()).unwrap_or(false);
316 if v { (true, vec![param.clone()]) } else { (false, vec![]) }
317 }
318 Self::Always => (true, vec![]),
319 Self::All(conds) => {
320 let mut consumed = Vec::new();
321 for c in conds {
322 let (ok, mut trig) = c.evaluate(params);
323 if !ok { return (false, vec![]); }
324 consumed.append(&mut trig);
325 }
326 (true, consumed)
327 }
328 Self::Any(conds) => {
329 for c in conds {
330 let (ok, trig) = c.evaluate(params);
331 if ok { return (true, trig); }
332 }
333 (false, vec![])
334 }
335 }
336 }
337}
338
339#[derive(Debug, Clone)]
342pub enum ParamValue {
343 Float(f32),
344 Bool(bool),
345 Int(i32),
346}
347
348impl ParamValue {
349 pub fn as_float(&self) -> Option<f32> {
350 match self {
351 Self::Float(v) => Some(*v),
352 Self::Int(v) => Some(*v as f32),
353 _ => None,
354 }
355 }
356 pub fn as_bool(&self) -> Option<bool> {
357 if let Self::Bool(v) = self { Some(*v) } else { None }
358 }
359 pub fn as_int(&self) -> Option<i32> {
360 if let Self::Int(v) = self { Some(*v) } else { None }
361 }
362}
363
364#[derive(Debug, Clone)]
367pub struct Transition {
368 pub from: String,
369 pub to: String,
370 pub condition: Condition,
371 pub duration: f32,
373 pub can_interrupt: bool,
375 pub exit_time: Option<f32>,
377 pub normalized_exit: Option<f32>,
379}
380
381impl Transition {
382 pub fn new(from: impl Into<String>, to: impl Into<String>, cond: Condition) -> Self {
383 Self {
384 from: from.into(),
385 to: to.into(),
386 condition: cond,
387 duration: 0.15,
388 can_interrupt: false,
389 exit_time: None,
390 normalized_exit: None,
391 }
392 }
393
394 pub fn with_duration(mut self, d: f32) -> Self { self.duration = d; self }
395 pub fn interruptible(mut self) -> Self { self.can_interrupt = true; self }
396 pub fn exit_at(mut self, t: f32) -> Self { self.exit_time = Some(t); self }
397 pub fn exit_normalized(mut self, t: f32) -> Self { self.normalized_exit = Some(t); self }
398}
399
400#[derive(Debug, Clone)]
403pub enum StateContent {
404 Clip(AnimationClip),
405 Tree(BlendTree),
406}
407
408#[derive(Debug, Clone)]
409pub struct AnimationState {
410 pub name: String,
411 pub content: StateContent,
412 pub speed: f32,
413 pub mirror: bool,
414 pub cyclic_offset: f32,
415 pub speed_curve: Option<MathFunction>,
417}
418
419impl AnimationState {
420 pub fn clip(clip: AnimationClip) -> Self {
421 let name = clip.name.clone();
422 Self {
423 name,
424 content: StateContent::Clip(clip),
425 speed: 1.0,
426 mirror: false,
427 cyclic_offset: 0.0,
428 speed_curve: None,
429 }
430 }
431
432 pub fn tree(name: impl Into<String>, tree: BlendTree) -> Self {
433 Self {
434 name: name.into(),
435 content: StateContent::Tree(tree),
436 speed: 1.0,
437 mirror: false,
438 cyclic_offset: 0.0,
439 speed_curve: None,
440 }
441 }
442
443 pub fn with_speed(mut self, s: f32) -> Self { self.speed = s; self }
444 pub fn mirrored(mut self) -> Self { self.mirror = true; self }
445
446 pub fn duration(&self) -> f32 {
447 match &self.content {
448 StateContent::Clip(c) => c.duration,
449 StateContent::Tree(t) => t.clips.iter().map(|c| c.duration).fold(0.0, f32::max),
450 }
451 }
452}
453
454#[derive(Debug, Clone)]
458pub struct AnimationLayer {
459 pub name: String,
460 pub weight: f32,
461 pub mask: Vec<String>,
463 pub additive: bool,
464 pub current_state: Option<String>,
466 pub current_time: f32,
467 pub transition: Option<ActiveTransition>,
468}
469
470#[derive(Debug, Clone)]
471pub struct ActiveTransition {
472 pub target_state: String,
473 pub progress: f32, pub duration: f32,
475 pub prev_time: f32,
476 pub prev_state: String,
477}
478
479impl AnimationLayer {
480 pub fn new(name: impl Into<String>) -> Self {
481 Self {
482 name: name.into(),
483 weight: 1.0,
484 mask: Vec::new(),
485 additive: false,
486 current_state: None,
487 current_time: 0.0,
488 transition: None,
489 }
490 }
491
492 pub fn with_weight(mut self, w: f32) -> Self { self.weight = w; self }
493 pub fn with_mask(mut self, mask: Vec<String>) -> Self { self.mask = mask; self }
494 pub fn as_additive(mut self) -> Self { self.additive = true; self }
495}
496
497pub struct AnimatorController {
501 pub states: HashMap<String, AnimationState>,
502 pub transitions: Vec<Transition>,
503 pub params: HashMap<String, ParamValue>,
504 pub layers: Vec<AnimationLayer>,
505 events: Vec<FiredEvent>,
507 pub clips: HashMap<String, AnimationClip>,
509}
510
511#[derive(Debug, Clone)]
512pub struct FiredEvent {
513 pub layer: String,
514 pub state: String,
515 pub tag: String,
516 pub payload: f32,
517}
518
519#[derive(Debug, Default, Clone)]
521pub struct AnimationOutput {
522 pub channels: HashMap<String, f32>,
523}
524
525impl AnimatorController {
526 pub fn new() -> Self {
527 let mut layers = vec![AnimationLayer::new("Base Layer")];
528 layers[0].weight = 1.0;
529 Self {
530 states: HashMap::new(),
531 transitions: Vec::new(),
532 params: HashMap::new(),
533 layers,
534 events: Vec::new(),
535 clips: HashMap::new(),
536 }
537 }
538
539 pub fn add_state(&mut self, state: AnimationState) {
542 self.states.insert(state.name.clone(), state);
543 }
544
545 pub fn add_clip(&mut self, clip: AnimationClip) {
546 self.clips.insert(clip.name.clone(), clip);
547 }
548
549 pub fn add_transition(&mut self, t: Transition) {
550 self.transitions.push(t);
551 }
552
553 pub fn add_layer(&mut self, layer: AnimationLayer) {
554 self.layers.push(layer);
555 }
556
557 pub fn set_float(&mut self, name: &str, v: f32) {
560 self.params.insert(name.to_owned(), ParamValue::Float(v));
561 }
562
563 pub fn set_bool(&mut self, name: &str, v: bool) {
564 self.params.insert(name.to_owned(), ParamValue::Bool(v));
565 }
566
567 pub fn set_int(&mut self, name: &str, v: i32) {
568 self.params.insert(name.to_owned(), ParamValue::Int(v));
569 }
570
571 pub fn set_trigger(&mut self, name: &str) {
573 self.params.insert(name.to_owned(), ParamValue::Bool(true));
574 }
575
576 pub fn get_float(&self, name: &str) -> f32 {
577 self.params.get(name).and_then(|p| p.as_float()).unwrap_or(0.0)
578 }
579
580 pub fn get_bool(&self, name: &str) -> bool {
581 self.params.get(name).and_then(|p| p.as_bool()).unwrap_or(false)
582 }
583
584 pub fn start(&mut self, state_name: &str) {
587 for layer in &mut self.layers {
588 layer.current_state = Some(state_name.to_owned());
589 layer.current_time = 0.0;
590 layer.transition = None;
591 }
592 }
593
594 pub fn start_layer(&mut self, layer_name: &str, state_name: &str) {
595 if let Some(layer) = self.layers.iter_mut().find(|l| l.name == layer_name) {
596 layer.current_state = Some(state_name.to_owned());
597 layer.current_time = 0.0;
598 layer.transition = None;
599 }
600 }
601
602 pub fn tick(&mut self, dt: f32) -> AnimationOutput {
607 let mut output = AnimationOutput::default();
608
609 let mut consumed_triggers: Vec<String> = Vec::new();
611
612 for layer in &mut self.layers {
613 if layer.current_state.is_none() { continue; }
614 let cur_name = layer.current_state.clone().unwrap();
615
616 if let Some(ref mut tr) = layer.transition {
618 tr.progress += dt / tr.duration.max(1e-4);
619 tr.prev_time += dt;
620 if tr.progress >= 1.0 {
621 let new_state = tr.target_state.clone();
623 layer.current_time = 0.0;
624 layer.current_state = Some(new_state);
625 layer.transition = None;
626 }
627 } else {
628 let applicable: Vec<Transition> = self.transitions.iter()
630 .filter(|t| t.from == cur_name || t.from == "*")
631 .cloned()
632 .collect();
633
634 let state_dur = self.states.get(&cur_name).map(|s| s.duration()).unwrap_or(1.0);
635
636 for trans in applicable {
637 if let Some(min_exit) = trans.exit_time {
639 if layer.current_time < min_exit { continue; }
640 }
641 if let Some(norm_exit) = trans.normalized_exit {
642 let norm = if state_dur > 1e-6 { layer.current_time / state_dur } else { 1.0 };
643 if norm < norm_exit { continue; }
644 }
645
646 let (ok, mut trig) = trans.condition.evaluate(&self.params);
647 if ok {
648 consumed_triggers.append(&mut trig);
649 let prev = cur_name.clone();
650 layer.transition = Some(ActiveTransition {
651 target_state: trans.to.clone(),
652 progress: 0.0,
653 duration: trans.duration,
654 prev_time: layer.current_time,
655 prev_state: prev,
656 });
657 break;
658 }
659 }
660
661 if let Some(state) = self.states.get(&cur_name) {
663 let speed_mod = if let Some(ref sf) = state.speed_curve {
664 let norm = if state.duration() > 1e-6 { layer.current_time / state.duration() } else { 0.0 };
665 sf.evaluate(norm, norm)
666 } else {
667 1.0
668 };
669 layer.current_time += dt * state.speed * speed_mod;
670 if state.duration() > 1e-6 {
671 if let StateContent::Clip(ref clip) = state.content {
672 if clip.looping {
673 layer.current_time %= clip.duration.max(1e-4);
674 } else {
675 layer.current_time = layer.current_time.min(clip.duration);
676 }
677 }
678 }
679 }
680 }
681
682 let sample_t = {
684 let dur = self.states.get(layer.current_state.as_deref().unwrap_or(""))
685 .map(|s| s.duration()).unwrap_or(1.0).max(1e-4);
686 (layer.current_time / dur).clamp(0.0, 1.0)
687 };
688
689 if let Some(state) = self.states.get(layer.current_state.as_deref().unwrap_or("")) {
690 let samples = match &state.content {
691 StateContent::Clip(c) => c.sample(sample_t),
692 StateContent::Tree(t) => t.sample(sample_t),
693 };
694 for (path, val) in samples {
695 let entry = output.channels.entry(path).or_insert(0.0);
696 if layer.additive {
697 *entry += val * layer.weight;
698 } else {
699 *entry = *entry * (1.0 - layer.weight) + val * layer.weight;
700 }
701 }
702 }
703 }
704
705 for key in consumed_triggers {
707 self.params.insert(key, ParamValue::Bool(false));
708 }
709
710 output
711 }
712
713 pub fn drain_events(&mut self) -> Vec<FiredEvent> {
715 std::mem::take(&mut self.events)
716 }
717
718 pub fn play(&mut self, state_name: &str) {
720 if let Some(layer) = self.layers.first_mut() {
721 layer.current_state = Some(state_name.to_owned());
722 layer.current_time = 0.0;
723 layer.transition = None;
724 }
725 }
726
727 pub fn cross_fade(&mut self, state_name: &str, duration: f32) {
729 if let Some(layer) = self.layers.first_mut() {
730 let prev = layer.current_state.clone().unwrap_or_default();
731 layer.transition = Some(ActiveTransition {
732 target_state: state_name.to_owned(),
733 progress: 0.0,
734 duration,
735 prev_time: layer.current_time,
736 prev_state: prev,
737 });
738 }
739 }
740
741 pub fn normalized_time(&self) -> f32 {
743 if let Some(layer) = self.layers.first() {
744 if let Some(name) = layer.current_state.as_deref() {
745 if let Some(state) = self.states.get(name) {
746 let dur = state.duration().max(1e-4);
747 return (layer.current_time / dur).clamp(0.0, 1.0);
748 }
749 }
750 }
751 0.0
752 }
753
754 pub fn current_state(&self) -> Option<&str> {
756 self.layers.first()?.current_state.as_deref()
757 }
758
759 pub fn is_transitioning(&self) -> bool {
761 self.layers.first().map(|l| l.transition.is_some()).unwrap_or(false)
762 }
763}
764
765impl Default for AnimatorController {
766 fn default() -> Self { Self::new() }
767}
768
769#[derive(Debug, Clone, Default)]
773pub struct RootMotion {
774 pub delta_position: glam::Vec3,
775 pub delta_rotation: f32,
776 pub delta_scale: glam::Vec3,
777}
778
779impl RootMotion {
780 pub fn from_output(output: &AnimationOutput) -> Self {
781 let get = |key: &str| output.channels.get(key).copied().unwrap_or(0.0);
782 Self {
783 delta_position: glam::Vec3::new(get("root.dx"), get("root.dy"), get("root.dz")),
784 delta_rotation: get("root.dr"),
785 delta_scale: glam::Vec3::ONE,
786 }
787 }
788
789 pub fn is_zero(&self) -> bool {
790 self.delta_position.length_squared() < 1e-10 && self.delta_rotation.abs() < 1e-6
791 }
792}
793
794pub fn mirror_output(output: &mut AnimationOutput) {
798 for (key, val) in &mut output.channels {
799 if key.ends_with(".x") || key.ends_with("_x") || key.contains("left") {
800 *val = -*val;
801 }
802 }
803}
804
805pub fn blend_outputs(a: &AnimationOutput, b: &AnimationOutput, alpha: f32) -> AnimationOutput {
809 let mut out = a.clone();
810 for (key, bv) in &b.channels {
811 let av = a.channels.get(key).copied().unwrap_or(0.0);
812 out.channels.insert(key.clone(), av + (bv - av) * alpha);
813 }
814 out
815}
816
817pub fn add_output(base: &AnimationOutput, additive: &AnimationOutput, weight: f32) -> AnimationOutput {
819 let mut out = base.clone();
820 for (key, av) in &additive.channels {
821 *out.channels.entry(key.clone()).or_insert(0.0) += av * weight;
822 }
823 out
824}