oxihuman_export/
camera_track_export.rs1#![allow(dead_code)]
4
5#[allow(dead_code)]
9#[derive(Debug, Clone, Copy)]
10pub struct CameraKeyframe {
11 pub time: f32,
12 pub position: [f32; 3],
13 pub target: [f32; 3],
14 pub fov_deg: f32,
15}
16
17#[allow(dead_code)]
19#[derive(Debug, Clone)]
20pub struct CameraTrackExport {
21 pub name: String,
22 pub keyframes: Vec<CameraKeyframe>,
23 pub fps: f32,
24}
25
26#[allow(dead_code)]
28pub fn new_camera_track(name: &str, fps: f32) -> CameraTrackExport {
29 CameraTrackExport {
30 name: name.to_string(),
31 keyframes: Vec::new(),
32 fps,
33 }
34}
35
36#[allow(dead_code)]
38pub fn add_camera_keyframe(track: &mut CameraTrackExport, kf: CameraKeyframe) {
39 track.keyframes.push(kf);
40 track.keyframes.sort_by(|a, b| {
41 a.time
42 .partial_cmp(&b.time)
43 .unwrap_or(std::cmp::Ordering::Equal)
44 });
45}
46
47#[allow(dead_code)]
49pub fn camera_keyframe_count(track: &CameraTrackExport) -> usize {
50 track.keyframes.len()
51}
52
53#[allow(dead_code)]
55pub fn camera_track_duration(track: &CameraTrackExport) -> f32 {
56 track.keyframes.last().map_or(0.0, |k| k.time)
57}
58
59#[allow(dead_code)]
61pub fn sample_camera_position(track: &CameraTrackExport, t: f32) -> [f32; 3] {
62 let kfs = &track.keyframes;
63 if kfs.is_empty() {
64 return [0.0; 3];
65 }
66 if t <= kfs[0].time {
67 return kfs[0].position;
68 }
69 if t >= kfs[kfs.len() - 1].time {
70 return kfs[kfs.len() - 1].position;
71 }
72 for i in 1..kfs.len() {
73 if kfs[i].time >= t {
74 let span = kfs[i].time - kfs[i - 1].time;
75 let alpha = if span > 0.0 {
76 (t - kfs[i - 1].time) / span
77 } else {
78 0.0
79 };
80 let a = kfs[i - 1].position;
81 let b = kfs[i].position;
82 return [
83 a[0] + alpha * (b[0] - a[0]),
84 a[1] + alpha * (b[1] - a[1]),
85 a[2] + alpha * (b[2] - a[2]),
86 ];
87 }
88 }
89 kfs[kfs.len() - 1].position
90}
91
92#[allow(dead_code)]
94pub fn validate_camera_track(track: &CameraTrackExport) -> bool {
95 track
96 .keyframes
97 .iter()
98 .all(|k| k.fov_deg > 0.0 && k.fov_deg < 180.0)
99}
100
101#[allow(dead_code)]
103pub fn camera_track_to_json(track: &CameraTrackExport) -> String {
104 format!(
105 "{{\"name\":\"{}\",\"keyframe_count\":{},\"duration\":{}}}",
106 track.name,
107 camera_keyframe_count(track),
108 camera_track_duration(track)
109 )
110}
111
112#[cfg(test)]
113mod tests {
114 use super::*;
115
116 fn kf(t: f32) -> CameraKeyframe {
117 CameraKeyframe {
118 time: t,
119 position: [t, 0.0, 0.0],
120 target: [0.0; 3],
121 fov_deg: 60.0,
122 }
123 }
124
125 #[test]
126 fn new_track_empty() {
127 let t = new_camera_track("main", 24.0);
128 assert_eq!(camera_keyframe_count(&t), 0);
129 }
130
131 #[test]
132 fn add_keyframe_increments() {
133 let mut t = new_camera_track("main", 24.0);
134 add_camera_keyframe(&mut t, kf(0.0));
135 assert_eq!(camera_keyframe_count(&t), 1);
136 }
137
138 #[test]
139 fn duration_last_kf() {
140 let mut t = new_camera_track("main", 24.0);
141 add_camera_keyframe(&mut t, kf(0.0));
142 add_camera_keyframe(&mut t, kf(2.0));
143 assert!((camera_track_duration(&t) - 2.0).abs() < 1e-5);
144 }
145
146 #[test]
147 fn sample_before_start() {
148 let mut t = new_camera_track("c", 24.0);
149 add_camera_keyframe(&mut t, kf(1.0));
150 let p = sample_camera_position(&t, 0.0);
151 assert!((p[0] - 1.0).abs() < 1e-5);
152 }
153
154 #[test]
155 fn sample_midpoint() {
156 let mut t = new_camera_track("c", 24.0);
157 add_camera_keyframe(&mut t, kf(0.0));
158 add_camera_keyframe(&mut t, kf(2.0));
159 let p = sample_camera_position(&t, 1.0);
160 assert!((p[0] - 1.0).abs() < 1e-5);
161 }
162
163 #[test]
164 fn validate_valid_fov() {
165 let mut t = new_camera_track("c", 24.0);
166 add_camera_keyframe(&mut t, kf(0.0));
167 assert!(validate_camera_track(&t));
168 }
169
170 #[test]
171 fn json_contains_name() {
172 let t = new_camera_track("hero_cam", 30.0);
173 let j = camera_track_to_json(&t);
174 assert!(j.contains("hero_cam"));
175 }
176
177 #[test]
178 fn fov_in_valid_range() {
179 let kf = kf(0.0);
180 assert!((0.0..=180.0).contains(&kf.fov_deg));
181 }
182
183 #[test]
184 fn keyframes_sorted_after_add() {
185 let mut t = new_camera_track("c", 24.0);
186 add_camera_keyframe(&mut t, kf(2.0));
187 add_camera_keyframe(&mut t, kf(0.5));
188 assert!(t.keyframes[0].time <= t.keyframes[1].time);
189 }
190
191 #[test]
192 fn sample_empty_returns_origin() {
193 let t = new_camera_track("c", 24.0);
194 let p = sample_camera_position(&t, 1.0);
195 assert!((p[0]).abs() < 1e-6);
196 }
197}