oxihuman_export/
action_export.rs1#![allow(dead_code)]
4
5#[derive(Debug, Clone)]
10pub struct ActionKeyframe {
11 pub time: f32,
12 pub value: f32,
13}
14
15#[derive(Debug, Clone)]
16pub struct ActionExport {
17 pub name: String,
18 pub keyframes: Vec<ActionKeyframe>,
19}
20
21pub fn new_action_export(name: &str) -> ActionExport {
22 ActionExport {
23 name: name.to_string(),
24 keyframes: vec![],
25 }
26}
27
28pub fn add_keyframe(action: &mut ActionExport, time: f32, value: f32) {
29 action.keyframes.push(ActionKeyframe { time, value });
30}
31
32pub fn keyframe_count(action: &ActionExport) -> usize {
33 action.keyframes.len()
34}
35
36pub fn action_duration(action: &ActionExport) -> f32 {
37 if action.keyframes.is_empty() {
38 return 0.0;
39 }
40 let min_t = action
41 .keyframes
42 .iter()
43 .map(|k| k.time)
44 .fold(f32::MAX, f32::min);
45 let max_t = action
46 .keyframes
47 .iter()
48 .map(|k| k.time)
49 .fold(f32::MIN, f32::max);
50 max_t - min_t
51}
52
53pub fn sample_action(action: &ActionExport, time: f32) -> f32 {
54 if action.keyframes.is_empty() {
55 return 0.0;
56 }
57 if action.keyframes.len() == 1 {
58 return action.keyframes[0].value;
59 }
60 let mut sorted: Vec<&ActionKeyframe> = action.keyframes.iter().collect();
61 sorted.sort_by(|a, b| {
62 a.time
63 .partial_cmp(&b.time)
64 .unwrap_or(std::cmp::Ordering::Equal)
65 });
66 if time <= sorted[0].time {
67 return sorted[0].value;
68 }
69 if time >= sorted[sorted.len() - 1].time {
70 return sorted[sorted.len() - 1].value;
71 }
72 for i in 0..sorted.len() - 1 {
73 if time >= sorted[i].time && time <= sorted[i + 1].time {
74 let dt = sorted[i + 1].time - sorted[i].time;
75 if dt < 1e-12 {
76 return sorted[i].value;
77 }
78 let t = (time - sorted[i].time) / dt;
79 return sorted[i].value + (sorted[i + 1].value - sorted[i].value) * t;
80 }
81 }
82 0.0
83}
84
85pub fn validate_action(action: &ActionExport) -> bool {
86 action.keyframes.iter().all(|k| k.time >= 0.0)
87}
88
89pub fn action_to_json(action: &ActionExport) -> String {
90 format!(
91 "{{\"name\":\"{}\",\"keyframes\":{},\"duration\":{:.6}}}",
92 action.name,
93 keyframe_count(action),
94 action_duration(action)
95 )
96}
97
98pub fn clear_keyframes(action: &mut ActionExport) {
99 action.keyframes.clear();
100}
101
102#[derive(Debug, Clone)]
106pub struct ActionExportSpec {
107 pub name: String,
108 pub fcurves: Vec<String>,
109 pub fps: f32,
110}
111
112pub fn new_action_export_spec(name: &str, fps: f32) -> ActionExportSpec {
114 ActionExportSpec {
115 name: name.to_string(),
116 fcurves: Vec::new(),
117 fps,
118 }
119}
120
121pub fn action_push_fcurve(a: &mut ActionExportSpec, path: &str) {
123 a.fcurves.push(path.to_string());
124}
125
126pub fn action_duration_frames(a: &ActionExportSpec) -> usize {
128 let _ = a;
129 0
130}
131
132pub fn action_spec_to_json(a: &ActionExportSpec) -> String {
134 format!(
135 "{{\"name\":\"{}\",\"fps\":{},\"fcurves\":{}}}",
136 a.name,
137 a.fps,
138 a.fcurves.len()
139 )
140}
141
142pub fn action_fcurve_count(a: &ActionExportSpec) -> usize {
144 a.fcurves.len()
145}
146
147#[cfg(test)]
148mod tests {
149 use super::*;
150
151 #[test]
152 fn test_new_action_export() {
153 let a = new_action_export("walk");
154 assert_eq!(a.name, "walk");
155 assert_eq!(keyframe_count(&a), 0);
156 }
157
158 #[test]
159 fn test_add_keyframe() {
160 let mut a = new_action_export("test");
161 add_keyframe(&mut a, 0.0, 1.0);
162 assert_eq!(keyframe_count(&a), 1);
163 }
164
165 #[test]
166 fn test_duration() {
167 let mut a = new_action_export("test");
168 add_keyframe(&mut a, 1.0, 0.0);
169 add_keyframe(&mut a, 3.0, 1.0);
170 assert!((action_duration(&a) - 2.0).abs() < 1e-6);
171 }
172
173 #[test]
174 fn test_action_push_fcurve() {
175 let mut a = new_action_export_spec("run", 24.0);
176 action_push_fcurve(&mut a, "loc.x");
177 assert_eq!(action_fcurve_count(&a), 1);
178 }
179
180 #[test]
181 fn test_action_spec_to_json() {
182 let a = new_action_export_spec("idle", 30.0);
183 let j = action_spec_to_json(&a);
184 assert!(j.contains("idle"));
185 }
186}