oxihuman_export/
keyframe_blend_export.rs1#![allow(dead_code)]
4
5#[allow(dead_code)]
8#[derive(Debug, Clone, Copy, PartialEq)]
9pub enum KeyBlendMode {
10 Linear,
11 Ease,
12 Constant,
13 Bezier,
14}
15
16#[allow(dead_code)]
17#[derive(Debug, Clone)]
18pub struct KeyframeBlendEntry {
19 pub time: f32,
20 pub value: f32,
21 pub blend_mode: KeyBlendMode,
22 pub blend_weight: f32,
23}
24
25#[allow(dead_code)]
26#[derive(Debug, Clone)]
27pub struct KeyframeBlendExport {
28 pub channel_name: String,
29 pub keys: Vec<KeyframeBlendEntry>,
30}
31
32#[allow(dead_code)]
33pub fn new_keyframe_blend_export(channel: &str) -> KeyframeBlendExport {
34 KeyframeBlendExport {
35 channel_name: channel.to_string(),
36 keys: Vec::new(),
37 }
38}
39
40#[allow(dead_code)]
41pub fn add_blend_key(exp: &mut KeyframeBlendExport, time: f32, value: f32, mode: KeyBlendMode) {
42 exp.keys.push(KeyframeBlendEntry {
43 time,
44 value,
45 blend_mode: mode,
46 blend_weight: 1.0,
47 });
48}
49
50#[allow(dead_code)]
51pub fn key_count_kbe(exp: &KeyframeBlendExport) -> usize {
52 exp.keys.len()
53}
54
55#[allow(dead_code)]
56pub fn channel_duration(exp: &KeyframeBlendExport) -> f32 {
57 exp.keys.iter().map(|k| k.time).fold(0.0_f32, f32::max)
58}
59
60#[allow(dead_code)]
61pub fn keys_of_mode(exp: &KeyframeBlendExport, mode: KeyBlendMode) -> usize {
62 exp.keys.iter().filter(|k| k.blend_mode == mode).count()
63}
64
65#[allow(dead_code)]
66pub fn sort_keys_by_time(exp: &mut KeyframeBlendExport) {
67 exp.keys.sort_by(|a, b| {
68 a.time
69 .partial_cmp(&b.time)
70 .unwrap_or(std::cmp::Ordering::Equal)
71 });
72}
73
74#[allow(dead_code)]
75pub fn sample_linear(exp: &KeyframeBlendExport, t: f32) -> f32 {
76 if exp.keys.is_empty() {
77 return 0.0;
78 }
79 let before: Vec<&KeyframeBlendEntry> = exp.keys.iter().filter(|k| k.time <= t).collect();
80 let after: Vec<&KeyframeBlendEntry> = exp.keys.iter().filter(|k| k.time > t).collect();
81 match (before.last(), after.first()) {
82 (Some(a), Some(b)) => {
83 let dt = b.time - a.time;
84 if dt.abs() < 1e-6 {
85 a.value
86 } else {
87 let alpha = (t - a.time) / dt;
88 a.value + alpha * (b.value - a.value)
89 }
90 }
91 (Some(a), None) => a.value,
92 (None, Some(b)) => b.value,
93 _ => 0.0,
94 }
95}
96
97#[allow(dead_code)]
98pub fn keyframe_blend_to_json(exp: &KeyframeBlendExport) -> String {
99 format!(
100 "{{\"channel\":\"{}\",\"key_count\":{}}}",
101 exp.channel_name,
102 key_count_kbe(exp)
103 )
104}
105
106#[allow(dead_code)]
107pub fn blend_mode_name(mode: KeyBlendMode) -> &'static str {
108 match mode {
109 KeyBlendMode::Linear => "linear",
110 KeyBlendMode::Ease => "ease",
111 KeyBlendMode::Constant => "constant",
112 KeyBlendMode::Bezier => "bezier",
113 }
114}
115
116#[cfg(test)]
117mod tests {
118 use super::*;
119
120 #[test]
121 fn test_empty() {
122 let exp = new_keyframe_blend_export("pos_x");
123 assert_eq!(key_count_kbe(&exp), 0);
124 }
125
126 #[test]
127 fn test_add_key() {
128 let mut exp = new_keyframe_blend_export("pos_x");
129 add_blend_key(&mut exp, 0.0, 0.0, KeyBlendMode::Linear);
130 assert_eq!(key_count_kbe(&exp), 1);
131 }
132
133 #[test]
134 fn test_channel_duration() {
135 let mut exp = new_keyframe_blend_export("pos_x");
136 add_blend_key(&mut exp, 0.0, 0.0, KeyBlendMode::Linear);
137 add_blend_key(&mut exp, 2.0, 1.0, KeyBlendMode::Linear);
138 assert!((channel_duration(&exp) - 2.0).abs() < 1e-5);
139 }
140
141 #[test]
142 fn test_keys_of_mode() {
143 let mut exp = new_keyframe_blend_export("rot_y");
144 add_blend_key(&mut exp, 0.0, 0.0, KeyBlendMode::Linear);
145 add_blend_key(&mut exp, 1.0, 0.5, KeyBlendMode::Ease);
146 assert_eq!(keys_of_mode(&exp, KeyBlendMode::Ease), 1);
147 }
148
149 #[test]
150 fn test_sort_keys() {
151 let mut exp = new_keyframe_blend_export("x");
152 add_blend_key(&mut exp, 2.0, 1.0, KeyBlendMode::Linear);
153 add_blend_key(&mut exp, 0.5, 0.5, KeyBlendMode::Linear);
154 sort_keys_by_time(&mut exp);
155 assert!((exp.keys[0].time - 0.5).abs() < 1e-5);
156 }
157
158 #[test]
159 fn test_sample_linear_midpoint() {
160 let mut exp = new_keyframe_blend_export("x");
161 add_blend_key(&mut exp, 0.0, 0.0, KeyBlendMode::Linear);
162 add_blend_key(&mut exp, 1.0, 1.0, KeyBlendMode::Linear);
163 let v = sample_linear(&exp, 0.5);
164 assert!((v - 0.5).abs() < 1e-5);
165 }
166
167 #[test]
168 fn test_blend_mode_name() {
169 assert_eq!(blend_mode_name(KeyBlendMode::Bezier), "bezier");
170 }
171
172 #[test]
173 fn test_json_output() {
174 let exp = new_keyframe_blend_export("scale");
175 let j = keyframe_blend_to_json(&exp);
176 assert!(j.contains("channel"));
177 }
178
179 #[test]
180 fn test_sample_empty_zero() {
181 let exp = new_keyframe_blend_export("x");
182 assert!((sample_linear(&exp, 0.5)).abs() < 1e-6);
183 }
184
185 #[test]
186 fn test_channel_name_stored() {
187 let exp = new_keyframe_blend_export("my_channel");
188 assert_eq!(exp.channel_name, "my_channel".to_string());
189 }
190
191 #[test]
192 fn test_constant_mode_name() {
193 assert_eq!(blend_mode_name(KeyBlendMode::Constant), "constant");
194 }
195}