use super::{CompiledGraph, CompiledState};
use crate::math::fract;
#[derive(Debug, Clone)]
pub struct StateFade {
pub from_state: usize,
pub from_clock: f32,
pub elapsed_secs: f32,
pub duration_secs: f32,
}
impl StateFade {
pub fn progress(&self) -> f32 {
(self.elapsed_secs / self.duration_secs.max(1e-6)).clamp(0.0, 1.0)
}
}
#[derive(Debug, Clone)]
pub struct GraphCursor {
pub state: usize,
pub clock: f32,
pub fade: Option<StateFade>,
}
impl GraphCursor {
pub fn start(graph: &CompiledGraph) -> Self {
Self {
state: graph.initial,
clock: 0.0,
fade: None,
}
}
pub fn advance(&mut self, graph: &CompiledGraph, params: &[f32], dt_secs: f32) {
let dt = dt_secs.max(0.0);
let state = &graph.states[self.state];
advance_clock(state, params, dt, &mut self.clock);
if let Some(fade) = self.fade.as_mut() {
fade.elapsed_secs += dt;
advance_clock(
&graph.states[fade.from_state],
params,
dt,
&mut fade.from_clock,
);
if fade.elapsed_secs >= fade.duration_secs {
self.fade = None;
}
}
let normalized = normalized_time(state, self.clock, params);
for tr in &state.transitions {
if let Some(gate) = tr.exit_time
&& normalized < gate
{
continue;
}
let hold = tr.conditions.iter().any(|c| {
let lhs = params.get(c.param).copied().unwrap_or(0.0);
!c.op.eval(lhs, c.value)
});
if hold {
continue;
}
self.fade = (tr.duration_secs > 0.0).then_some(StateFade {
from_state: self.state,
from_clock: self.clock,
elapsed_secs: 0.0,
duration_secs: tr.duration_secs,
});
self.state = tr.to;
self.clock = 0.0;
break;
}
}
}
fn advance_clock(state: &CompiledState, params: &[f32], dt: f32, clock: &mut f32) {
if state.play.sync() {
let weights = state.play.weights(params);
let eff = state.play.effective_duration(&weights).max(1e-6);
*clock += dt * state.rate / eff;
} else {
*clock += dt * state.rate;
}
}
pub fn normalized_time(state: &CompiledState, clock: f32, params: &[f32]) -> f32 {
let phase = if state.play.sync() {
clock
} else {
let weights = state.play.weights(params);
let eff = state.play.effective_duration(&weights);
if eff <= 1e-6 {
return 1.0;
}
clock / eff
};
if state.looping {
fract(phase)
} else {
phase.clamp(0.0, 1.0)
}
}