Skip to main content

concinnity_core/gfx/anim_graph/
graph.rs

1// The compiled state machine: its states, transitions, and parameters, plus the
2// clip-duration refresh a hot-reload applies.
3
4use super::StatePlay;
5use alloc::string::String;
6use alloc::vec::Vec;
7
8/// Comparison operator for a transition condition, evaluated as
9/// `parameter <op> value`. `eq` / `ne` compare exactly and are mainly useful
10/// for flag-like parameters holding whole numbers such as 0 and 1.
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)]
12#[serde(rename_all = "lowercase")]
13pub enum CmpOp {
14    /// Less than.
15    #[default]
16    Lt,
17    /// Less than or equal.
18    Le,
19    /// Greater than.
20    Gt,
21    /// Greater than or equal.
22    Ge,
23    /// Equal (exact).
24    Eq,
25    /// Not equal (exact).
26    Ne,
27}
28
29impl CmpOp {
30    /// Evaluate `lhs <op> rhs`.
31    pub fn eval(self, lhs: f32, rhs: f32) -> bool {
32        match self {
33            CmpOp::Lt => lhs < rhs,
34            CmpOp::Le => lhs <= rhs,
35            CmpOp::Gt => lhs > rhs,
36            CmpOp::Ge => lhs >= rhs,
37            CmpOp::Eq => lhs == rhs,
38            CmpOp::Ne => lhs != rhs,
39        }
40    }
41}
42
43/// One compiled transition condition: parameter names are resolved to indices
44/// into the graph's parameter vector at compile time, so evaluation is a
45/// direct slice read (the runtime blob interner is empty; nothing resolves
46/// names at runtime).
47#[derive(Debug, Clone)]
48pub struct CompiledCondition {
49    /// Index into the graph's parameter vector.
50    pub param: usize,
51    /// Comparison applied between the parameter and `value`.
52    pub op: CmpOp,
53    /// The value the parameter is compared against.
54    pub value: f32,
55}
56
57/// One compiled outgoing transition. Conditions AND together; an empty list is
58/// always true (useful with `exit_time` alone).
59#[derive(Debug, Clone)]
60pub struct CompiledTransition {
61    /// Index of the state this transition enters.
62    pub to: usize,
63    /// Crossfade length between the outgoing and incoming state poses. Zero
64    /// snaps.
65    pub duration_secs: f32,
66    /// When set, the transition is gated until the state's normalized time
67    /// reaches this value (see `normalized_time`).
68    pub exit_time: Option<f32>,
69    /// Conditions that must all hold; an empty list is always true.
70    pub conditions: Vec<CompiledCondition>,
71}
72
73/// One compiled state: what it plays (a clip or a blendspace, at `rate`) and
74/// its outgoing transitions in declaration order (first match wins).
75#[derive(Debug, Clone)]
76pub struct CompiledState {
77    /// The state's authored name.
78    pub name: String,
79    /// Clock scale; 1.0 plays at authored speed.
80    pub rate: f32,
81    /// Whether this state's clock wraps. Single-clip states default to the
82    /// clip's own flag, blendspaces default to wrapping; `loop_override`
83    /// wins over both.
84    pub looping: bool,
85    /// What the state plays.
86    pub play: StatePlay,
87    /// Outgoing transitions in declaration order; first match wins.
88    pub transitions: Vec<CompiledTransition>,
89}
90
91/// A graph parameter: a named float, seeded to `default`.
92#[derive(Debug, Clone)]
93pub struct ParamSpec {
94    /// The parameter's authored name.
95    pub name: String,
96    /// Value the parameter is seeded with.
97    pub default: f32,
98}
99
100/// A compiled animation state machine. Built once from the `AnimationGraph` asset;
101/// read-only afterwards except for clip-duration refresh on hot-reload.
102#[derive(Debug, Clone)]
103pub struct CompiledGraph {
104    /// The graph's parameters, in index order.
105    pub params: Vec<ParamSpec>,
106    /// The graph's states, in index order.
107    pub states: Vec<CompiledState>,
108    /// Index of the state the graph starts in.
109    pub initial: usize,
110}
111
112impl CompiledGraph {
113    /// Index of a parameter by name, for surfaces (debug commands) that still
114    /// speak names.
115    pub fn param_index(&self, name: &str) -> Option<usize> {
116        self.params.iter().position(|p| p.name == name)
117    }
118
119    /// The parameter vector seeded to each parameter's declared default.
120    pub fn default_params(&self) -> Vec<f32> {
121        self.params.iter().map(|p| p.default).collect()
122    }
123
124    /// Update every member playing `clip` to a new duration, so wrap and
125    /// exit-time math keep tracking a hot-reloaded clip.
126    pub fn refresh_clip_duration(&mut self, clip: usize, duration_secs: f32) {
127        for state in &mut self.states {
128            for member in state.play.members_mut() {
129                if member.clip == clip {
130                    member.duration_secs = duration_secs;
131                }
132            }
133        }
134    }
135}