use alloc::vec::Vec;
#[derive(Debug, Clone)]
pub struct ClipPlay {
pub clip: usize,
pub duration_secs: f32,
}
#[derive(Debug, Clone)]
pub struct Blend1D {
pub param: usize,
pub thresholds: Vec<f32>,
pub plays: Vec<ClipPlay>,
pub sync: bool,
}
#[derive(Debug, Clone)]
pub struct Blend2D {
pub(crate) param_x: usize,
pub(crate) param_y: usize,
pub x_values: Vec<f32>,
pub y_values: Vec<f32>,
pub plays: Vec<ClipPlay>,
pub sync: bool,
}
#[derive(Debug, Clone)]
pub enum StatePlay {
Clip(ClipPlay),
Blend1D(Blend1D),
Blend2D(Blend2D),
}
impl StatePlay {
pub fn sync(&self) -> bool {
match self {
StatePlay::Clip(_) => false,
StatePlay::Blend1D(b) => b.sync,
StatePlay::Blend2D(b) => b.sync,
}
}
pub fn members(&self) -> &[ClipPlay] {
match self {
StatePlay::Clip(play) => core::slice::from_ref(play),
StatePlay::Blend1D(b) => &b.plays,
StatePlay::Blend2D(b) => &b.plays,
}
}
pub(crate) fn members_mut(&mut self) -> &mut [ClipPlay] {
match self {
StatePlay::Clip(play) => core::slice::from_mut(play),
StatePlay::Blend1D(b) => &mut b.plays,
StatePlay::Blend2D(b) => &mut b.plays,
}
}
pub fn weights(&self, params: &[f32]) -> Vec<f32> {
let mut out = Vec::new();
self.weights_into(params, &mut out);
out
}
pub fn weights_into(&self, params: &[f32], out: &mut Vec<f32>) {
let at = |i: usize| params.get(i).copied().unwrap_or(0.0);
out.clear();
match self {
StatePlay::Clip(_) => out.push(1.0),
StatePlay::Blend1D(b) => blend1d_weights_into(&b.thresholds, at(b.param), out),
StatePlay::Blend2D(b) => {
blend2d_weights_into(&b.x_values, &b.y_values, at(b.param_x), at(b.param_y), out)
}
}
}
pub fn effective_duration(&self, weights: &[f32]) -> f32 {
let members = self.members();
let mut total_w = 0.0f32;
let mut acc = 0.0f32;
for (member, &w) in members.iter().zip(weights) {
let w = w.max(0.0);
acc += w * member.duration_secs;
total_w += w;
}
if total_w <= 1e-6 {
members.first().map(|m| m.duration_secs).unwrap_or(0.0)
} else {
acc / total_w
}
}
}
pub fn blend1d_weights(thresholds: &[f32], x: f32) -> Vec<f32> {
let mut weights = Vec::new();
blend1d_weights_into(thresholds, x, &mut weights);
weights
}
pub(super) fn blend1d_weights_into(thresholds: &[f32], x: f32, weights: &mut Vec<f32>) {
weights.clear();
weights.resize(thresholds.len(), 0.0);
let Some((&first, &last)) = thresholds.first().zip(thresholds.last()) else {
return;
};
if x <= first {
weights[0] = 1.0;
return;
}
if x >= last {
*weights.last_mut().expect("non-empty") = 1.0;
return;
}
for i in 0..thresholds.len() - 1 {
let (a, b) = (thresholds[i], thresholds[i + 1]);
if x >= a && x <= b {
let f = (x - a) / (b - a).max(1e-6);
weights[i] = 1.0 - f;
weights[i + 1] = f;
break;
}
}
}
pub fn blend2d_weights(x_values: &[f32], y_values: &[f32], x: f32, y: f32) -> Vec<f32> {
let mut weights = Vec::new();
blend2d_weights_into(x_values, y_values, x, y, &mut weights);
weights
}
pub(super) fn blend2d_weights_into(
x_values: &[f32],
y_values: &[f32],
x: f32,
y: f32,
weights: &mut Vec<f32>,
) {
weights.clear();
weights.resize(x_values.len() * y_values.len(), 0.0);
if x_values.is_empty() || y_values.is_empty() {
return;
}
let (ix, fx) = axis_segment(x_values, x);
let (iy, fy) = axis_segment(y_values, y);
let nx = x_values.len();
let ix1 = (ix + 1).min(nx - 1);
let iy1 = (iy + 1).min(y_values.len() - 1);
weights[iy * nx + ix] += (1.0 - fx) * (1.0 - fy);
weights[iy * nx + ix1] += fx * (1.0 - fy);
weights[iy1 * nx + ix] += (1.0 - fx) * fy;
weights[iy1 * nx + ix1] += fx * fy;
}
fn axis_segment(values: &[f32], v: f32) -> (usize, f32) {
if v <= values[0] || values.len() == 1 {
return (0, 0.0);
}
if v >= values[values.len() - 1] {
return (values.len() - 1, 0.0);
}
for i in 0..values.len() - 1 {
let (a, b) = (values[i], values[i + 1]);
if v >= a && v <= b {
return (i, (v - a) / (b - a).max(1e-6));
}
}
(values.len() - 1, 0.0)
}