#![allow(dead_code)]
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct ClipKey {
pub time: f32,
pub weight: f32,
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct ExpressionClip {
name: String,
keys: Vec<ClipKey>,
looping: bool,
}
#[allow(dead_code)]
pub fn new_expression_clip(name: &str) -> ExpressionClip {
ExpressionClip {
name: name.to_string(),
keys: Vec::new(),
looping: false,
}
}
#[allow(dead_code)]
pub fn add_clip_key(clip: &mut ExpressionClip, time: f32, weight: f32) {
clip.keys.push(ClipKey { time, weight });
clip.keys.sort_by(|a, b| a.time.partial_cmp(&b.time).unwrap_or(std::cmp::Ordering::Equal));
}
#[allow(dead_code)]
pub fn evaluate_clip(clip: &ExpressionClip, t: f32) -> f32 {
if clip.keys.is_empty() {
return 0.0;
}
if clip.keys.len() == 1 {
return clip.keys[0].weight;
}
let clamped = if clip.looping && clip_duration(clip) > 0.0 {
t % clip_duration(clip)
} else {
t.clamp(clip.keys[0].time, clip.keys[clip.keys.len() - 1].time)
};
for i in 0..clip.keys.len() - 1 {
let a = &clip.keys[i];
let b = &clip.keys[i + 1];
if (a.time..=b.time).contains(&clamped) {
let span = b.time - a.time;
if span.abs() < 1e-9 {
return a.weight;
}
let frac = (clamped - a.time) / span;
return a.weight + (b.weight - a.weight) * frac;
}
}
clip.keys.last().map_or(0.0, |k| k.weight)
}
#[allow(dead_code)]
pub fn clip_duration(clip: &ExpressionClip) -> f32 {
if clip.keys.len() < 2 {
return 0.0;
}
clip.keys.last().map_or(0.0, |k| k.time) - clip.keys[0].time
}
#[allow(dead_code)]
pub fn clip_key_count(clip: &ExpressionClip) -> usize {
clip.keys.len()
}
#[allow(dead_code)]
pub fn clip_to_json(clip: &ExpressionClip) -> String {
let keys_str: Vec<String> = clip
.keys
.iter()
.map(|k| format!("{{\"time\":{},\"weight\":{}}}", k.time, k.weight))
.collect();
format!(
"{{\"name\":\"{}\",\"looping\":{},\"keys\":[{}]}}",
clip.name,
clip.looping,
keys_str.join(",")
)
}
#[allow(dead_code)]
pub fn clip_loop(clip: &mut ExpressionClip, looping: bool) {
clip.looping = looping;
}
#[allow(dead_code)]
pub fn clip_reverse(clip: &mut ExpressionClip) {
if clip.keys.len() < 2 {
return;
}
let dur = clip_duration(clip);
let start = clip.keys[0].time;
clip.keys.reverse();
for key in &mut clip.keys {
key.time = start + dur - (key.time - start);
}
clip.keys.sort_by(|a, b| a.time.partial_cmp(&b.time).unwrap_or(std::cmp::Ordering::Equal));
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new_clip() {
let c = new_expression_clip("test");
assert_eq!(clip_key_count(&c), 0);
}
#[test]
fn test_add_key() {
let mut c = new_expression_clip("test");
add_clip_key(&mut c, 0.0, 0.0);
add_clip_key(&mut c, 1.0, 1.0);
assert_eq!(clip_key_count(&c), 2);
}
#[test]
fn test_evaluate_empty() {
let c = new_expression_clip("e");
assert!((evaluate_clip(&c, 0.5) - 0.0).abs() < 1e-6);
}
#[test]
fn test_evaluate_single() {
let mut c = new_expression_clip("s");
add_clip_key(&mut c, 0.0, 0.7);
assert!((evaluate_clip(&c, 0.5) - 0.7).abs() < 1e-6);
}
#[test]
fn test_evaluate_interpolation() {
let mut c = new_expression_clip("i");
add_clip_key(&mut c, 0.0, 0.0);
add_clip_key(&mut c, 1.0, 1.0);
assert!((evaluate_clip(&c, 0.5) - 0.5).abs() < 1e-6);
}
#[test]
fn test_clip_duration() {
let mut c = new_expression_clip("d");
add_clip_key(&mut c, 1.0, 0.0);
add_clip_key(&mut c, 3.0, 1.0);
assert!((clip_duration(&c) - 2.0).abs() < 1e-6);
}
#[test]
fn test_clip_to_json() {
let c = new_expression_clip("j");
let json = clip_to_json(&c);
assert!(json.contains("\"name\":\"j\""));
}
#[test]
fn test_clip_loop() {
let mut c = new_expression_clip("l");
clip_loop(&mut c, true);
assert!(c.looping);
}
#[test]
fn test_clip_reverse() {
let mut c = new_expression_clip("r");
add_clip_key(&mut c, 0.0, 0.0);
add_clip_key(&mut c, 1.0, 1.0);
clip_reverse(&mut c);
assert!((evaluate_clip(&c, 0.0) - 1.0).abs() < 1e-6);
}
#[test]
fn test_clip_reverse_single() {
let mut c = new_expression_clip("r");
add_clip_key(&mut c, 0.0, 0.5);
clip_reverse(&mut c);
assert_eq!(clip_key_count(&c), 1);
}
}