1#[allow(dead_code)]
7pub struct MotionFrame {
8 pub time: f32,
9 pub pose: Vec<f32>,
10}
11
12#[allow(dead_code)]
13pub struct MotionClip {
14 pub name: String,
15 pub frames: Vec<MotionFrame>,
16 pub fps: f32,
17}
18
19#[allow(dead_code)]
20pub struct WarpCurve {
21 pub keys: Vec<(f32, f32)>,
22}
23
24#[allow(dead_code)]
25pub enum WarpMode {
26 Linear,
27 Bezier,
28 Hold,
29}
30
31#[allow(dead_code)]
32pub struct WarpedClip {
33 pub frames: Vec<MotionFrame>,
34 pub original_duration: f32,
35 pub warped_duration: f32,
36}
37
38#[allow(dead_code)]
39pub fn clip_duration(clip: &MotionClip) -> f32 {
40 if clip.frames.is_empty() {
41 return 0.0;
42 }
43 clip.frames[clip.frames.len() - 1].time - clip.frames[0].time
44}
45
46#[allow(dead_code)]
47pub fn sample_clip(clip: &MotionClip, time: f32) -> Vec<f32> {
48 if clip.frames.is_empty() {
49 return Vec::new();
50 }
51 if clip.frames.len() == 1 {
52 return clip.frames[0].pose.clone();
53 }
54 let first = &clip.frames[0];
55 let last = &clip.frames[clip.frames.len() - 1];
56 if time <= first.time {
57 return first.pose.clone();
58 }
59 if time >= last.time {
60 return last.pose.clone();
61 }
62 let mut lo = 0usize;
64 let mut hi = clip.frames.len() - 1;
65 while lo + 1 < hi {
66 let mid = (lo + hi) / 2;
67 if clip.frames[mid].time <= time {
68 lo = mid;
69 } else {
70 hi = mid;
71 }
72 }
73 let fa = &clip.frames[lo];
74 let fb = &clip.frames[hi];
75 let span = fb.time - fa.time;
76 let t = if span.abs() < 1e-9 {
77 0.0
78 } else {
79 (time - fa.time) / span
80 };
81 pose_lerp(&fa.pose, &fb.pose, t)
82}
83
84#[allow(dead_code)]
85pub fn warp_time(curve: &WarpCurve, t: f32) -> f32 {
86 if curve.keys.is_empty() {
87 return t;
88 }
89 if curve.keys.len() == 1 {
90 return curve.keys[0].1;
91 }
92 let first = curve.keys[0];
93 let last = curve.keys[curve.keys.len() - 1];
94 if t <= first.0 {
95 return first.1;
96 }
97 if t >= last.0 {
98 return last.1;
99 }
100 let mut lo = 0usize;
101 let mut hi = curve.keys.len() - 1;
102 while lo + 1 < hi {
103 let mid = (lo + hi) / 2;
104 if curve.keys[mid].0 <= t {
105 lo = mid;
106 } else {
107 hi = mid;
108 }
109 }
110 let (t0, v0) = curve.keys[lo];
111 let (t1, v1) = curve.keys[hi];
112 let span = t1 - t0;
113 let alpha = if span.abs() < 1e-9 {
114 0.0
115 } else {
116 (t - t0) / span
117 };
118 v0 + (v1 - v0) * alpha
119}
120
121#[allow(dead_code)]
122pub fn apply_warp(clip: &MotionClip, curve: &WarpCurve, output_fps: f32) -> WarpedClip {
123 let orig_duration = clip_duration(clip);
124 if clip.frames.is_empty() || output_fps <= 0.0 {
125 return WarpedClip {
126 frames: Vec::new(),
127 original_duration: orig_duration,
128 warped_duration: 0.0,
129 };
130 }
131 let warped_end = warp_time(curve, orig_duration);
132 let warped_duration = warped_end;
133 let frame_count = ((warped_duration * output_fps).round() as usize).max(1);
134 let mut frames = Vec::with_capacity(frame_count);
135 for i in 0..frame_count {
136 let warped_t = i as f32 / output_fps;
137 let orig_t = warp_time(curve, warped_t);
138 let pose = sample_clip(clip, orig_t);
139 frames.push(MotionFrame {
140 time: warped_t,
141 pose,
142 });
143 }
144 WarpedClip {
145 frames,
146 original_duration: orig_duration,
147 warped_duration,
148 }
149}
150
151#[allow(dead_code)]
152pub fn speed_scale_clip(clip: &MotionClip, factor: f32) -> WarpedClip {
153 let orig_duration = clip_duration(clip);
154 let factor = factor.max(1e-6);
155 let warped_duration = orig_duration / factor;
156 let output_fps = clip.fps.max(1.0);
157 let frame_count = ((warped_duration * output_fps).round() as usize).max(1);
158 let mut frames = Vec::with_capacity(frame_count);
159 for i in 0..frame_count {
160 let warped_t = i as f32 / output_fps;
161 let orig_t = warped_t * factor;
162 let pose = sample_clip(clip, orig_t);
163 frames.push(MotionFrame {
164 time: warped_t,
165 pose,
166 });
167 }
168 WarpedClip {
169 frames,
170 original_duration: orig_duration,
171 warped_duration,
172 }
173}
174
175#[allow(dead_code)]
176pub fn reverse_clip(clip: &MotionClip) -> MotionClip {
177 let duration = clip_duration(clip);
178 let frames: Vec<MotionFrame> = clip
179 .frames
180 .iter()
181 .rev()
182 .map(|f| MotionFrame {
183 time: duration - f.time,
184 pose: f.pose.clone(),
185 })
186 .collect();
187 MotionClip {
188 name: format!("{}_reversed", clip.name),
189 frames,
190 fps: clip.fps,
191 }
192}
193
194#[allow(dead_code)]
195pub fn trim_clip(clip: &MotionClip, start: f32, end: f32) -> MotionClip {
196 let frames: Vec<MotionFrame> = clip
197 .frames
198 .iter()
199 .filter(|f| f.time >= start && f.time <= end)
200 .map(|f| MotionFrame {
201 time: f.time - start,
202 pose: f.pose.clone(),
203 })
204 .collect();
205 MotionClip {
206 name: clip.name.clone(),
207 frames,
208 fps: clip.fps,
209 }
210}
211
212#[allow(dead_code)]
213pub fn blend_clips(
214 a: &MotionClip,
215 b: &MotionClip,
216 blend_weight: f32,
217 output_fps: f32,
218) -> MotionClip {
219 let t = blend_weight.clamp(0.0, 1.0);
220 let dur_a = clip_duration(a);
221 let dur_b = clip_duration(b);
222 let duration = dur_a * (1.0 - t) + dur_b * t;
223 let fps = output_fps.max(1.0);
224 let frame_count = ((duration * fps).round() as usize).max(1);
225 let mut frames = Vec::with_capacity(frame_count);
226 for i in 0..frame_count {
227 let time = i as f32 / fps;
228 let pa = sample_clip(a, time);
229 let pb = sample_clip(b, time);
230 let pose = pose_lerp(&pa, &pb, t);
231 frames.push(MotionFrame { time, pose });
232 }
233 MotionClip {
234 name: format!("blend_{}_{}", a.name, b.name),
235 frames,
236 fps,
237 }
238}
239
240#[allow(dead_code)]
241pub fn concat_clips(clips: &[MotionClip]) -> MotionClip {
242 let mut frames = Vec::new();
243 let mut offset = 0.0f32;
244 let fps = clips.first().map(|c| c.fps).unwrap_or(30.0);
245 for clip in clips {
246 for f in &clip.frames {
247 frames.push(MotionFrame {
248 time: f.time + offset,
249 pose: f.pose.clone(),
250 });
251 }
252 offset += clip_duration(clip);
253 }
254 MotionClip {
255 name: "concat".to_string(),
256 frames,
257 fps,
258 }
259}
260
261#[allow(dead_code)]
262pub fn loop_clip(clip: &MotionClip, loops: u32) -> MotionClip {
263 if loops == 0 {
264 return MotionClip {
265 name: clip.name.clone(),
266 frames: Vec::new(),
267 fps: clip.fps,
268 };
269 }
270 let duration = clip_duration(clip);
271 let mut frames = Vec::new();
272 for l in 0..loops {
273 let offset = duration * l as f32;
274 for f in &clip.frames {
275 frames.push(MotionFrame {
276 time: f.time + offset,
277 pose: f.pose.clone(),
278 });
279 }
280 }
281 MotionClip {
282 name: format!("{}_loop{}", clip.name, loops),
283 frames,
284 fps: clip.fps,
285 }
286}
287
288#[allow(dead_code)]
289pub fn identity_warp_curve() -> WarpCurve {
290 WarpCurve {
291 keys: vec![(0.0, 0.0), (1.0, 1.0)],
292 }
293}
294
295#[allow(dead_code)]
296pub fn linear_warp_curve(speed_factor: f32) -> WarpCurve {
297 let factor = speed_factor.max(1e-6);
298 WarpCurve {
299 keys: vec![(0.0, 0.0), (1.0, 1.0 / factor)],
300 }
301}
302
303#[allow(dead_code)]
304pub fn pose_lerp(a: &[f32], b: &[f32], t: f32) -> Vec<f32> {
305 let len = a.len().min(b.len());
306 let mut out = Vec::with_capacity(len);
307 for i in 0..len {
308 out.push(a[i] + (b[i] - a[i]) * t);
309 }
310 out
311}
312
313#[allow(dead_code)]
314fn make_test_clip(name: &str, frames: usize, fps: f32, joints: usize) -> MotionClip {
315 let dt = 1.0 / fps;
316 MotionClip {
317 name: name.to_string(),
318 frames: (0..frames)
319 .map(|i| MotionFrame {
320 time: i as f32 * dt,
321 pose: vec![i as f32; joints],
322 })
323 .collect(),
324 fps,
325 }
326}
327
328#[cfg(test)]
329mod tests {
330 use super::*;
331
332 #[test]
333 fn test_clip_duration() {
334 let clip = make_test_clip("test", 31, 30.0, 4);
335 let d = clip_duration(&clip);
336 assert!((d - 1.0).abs() < 0.01);
337 }
338
339 #[test]
340 fn test_clip_duration_empty() {
341 let clip = MotionClip {
342 name: "empty".to_string(),
343 frames: Vec::new(),
344 fps: 30.0,
345 };
346 assert_eq!(clip_duration(&clip), 0.0);
347 }
348
349 #[test]
350 fn test_sample_at_t0_gives_first_frame() {
351 let clip = make_test_clip("test", 10, 30.0, 3);
352 let pose = sample_clip(&clip, 0.0);
353 assert_eq!(pose, vec![0.0_f32, 0.0, 0.0]);
354 }
355
356 #[test]
357 fn test_sample_at_end_gives_last_frame() {
358 let clip = make_test_clip("test", 5, 30.0, 2);
359 let d = clip_duration(&clip);
360 let pose = sample_clip(&clip, d);
361 assert_eq!(pose, vec![4.0_f32, 4.0]);
362 }
363
364 #[test]
365 fn test_speed_scale_halves_duration() {
366 let clip = make_test_clip("test", 31, 30.0, 2);
367 let orig_dur = clip_duration(&clip);
368 let warped = speed_scale_clip(&clip, 2.0);
369 assert!((warped.warped_duration - orig_dur / 2.0).abs() < 0.05);
370 }
371
372 #[test]
373 fn test_speed_scale_doubles_duration() {
374 let clip = make_test_clip("test", 31, 30.0, 2);
375 let orig_dur = clip_duration(&clip);
376 let warped = speed_scale_clip(&clip, 0.5);
377 assert!((warped.warped_duration - orig_dur * 2.0).abs() < 0.05);
378 }
379
380 #[test]
381 fn test_reverse_clip_inverts() {
382 let clip = make_test_clip("test", 5, 30.0, 1);
383 let rev = reverse_clip(&clip);
384 assert_eq!(rev.frames.len(), 5);
385 let orig_last_pose = clip.frames.last().expect("should succeed").pose.clone();
387 assert!((rev.frames[0].time).abs() < 1e-4);
388 assert!((rev.frames[0].pose[0] - orig_last_pose[0]).abs() < 1e-4);
390 }
391
392 #[test]
393 fn test_trim_clip_shrinks() {
394 let clip = make_test_clip("test", 31, 30.0, 2);
395 let trimmed = trim_clip(&clip, 0.0, 0.5);
396 let dur = clip_duration(&trimmed);
397 assert!(dur <= 0.5 + 0.04);
398 }
399
400 #[test]
401 fn test_blend_identical_clips() {
402 let a = make_test_clip("a", 10, 30.0, 3);
403 let b = make_test_clip("b", 10, 30.0, 3);
404 let blended = blend_clips(&a, &b, 0.5, 30.0);
405 let pa = sample_clip(&a, 0.0);
406 let pb = sample_clip(&blended, 0.0);
407 for (va, vb) in pa.iter().zip(pb.iter()) {
408 assert!((va - vb).abs() < 1e-4);
409 }
410 }
411
412 #[test]
413 fn test_concat_clips() {
414 let a = make_test_clip("a", 31, 30.0, 2);
415 let b = make_test_clip("b", 31, 30.0, 2);
416 let dur_a = clip_duration(&a);
417 let dur_b = clip_duration(&b);
418 let cat = concat_clips(&[a, b]);
419 let dur = clip_duration(&cat);
420 assert!((dur - (dur_a + dur_b)).abs() < 0.05);
421 }
422
423 #[test]
424 fn test_loop_doubles_duration() {
425 let clip = make_test_clip("test", 31, 30.0, 2);
426 let orig_dur = clip_duration(&clip);
427 let looped = loop_clip(&clip, 2);
428 let loop_dur = clip_duration(&looped);
429 assert!((loop_dur - orig_dur * 2.0).abs() < 0.05);
430 }
431
432 #[test]
433 fn test_loop_zero() {
434 let clip = make_test_clip("test", 5, 30.0, 2);
435 let looped = loop_clip(&clip, 0);
436 assert!(looped.frames.is_empty());
437 }
438
439 #[test]
440 fn test_identity_warp() {
441 let curve = identity_warp_curve();
442 assert!((warp_time(&curve, 0.5) - 0.5).abs() < 1e-4);
443 }
444
445 #[test]
446 fn test_linear_warp_curve() {
447 let curve = linear_warp_curve(2.0);
448 assert!((warp_time(&curve, 1.0) - 0.5).abs() < 1e-4);
450 }
451
452 #[test]
453 fn test_pose_lerp() {
454 let a = vec![0.0_f32, 0.0, 0.0];
455 let b = vec![2.0_f32, 4.0, 6.0];
456 let result = pose_lerp(&a, &b, 0.5);
457 assert!((result[0] - 1.0).abs() < 1e-5);
458 assert!((result[1] - 2.0).abs() < 1e-5);
459 assert!((result[2] - 3.0).abs() < 1e-5);
460 }
461}