Skip to main content

oxihuman_export/
camera_rig_export.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! Camera rig export: export camera rigs with positions and targets.
6
7/// A camera rig keyframe.
8#[allow(dead_code)]
9#[derive(Debug, Clone)]
10pub struct CameraRigKeyframe {
11    pub time: f32,
12    pub position: [f32; 3],
13    pub target: [f32; 3],
14    pub fov: f32,
15}
16
17/// Camera rig export.
18#[allow(dead_code)]
19#[derive(Debug, Clone)]
20pub struct CameraRigExport {
21    pub name: String,
22    pub keyframes: Vec<CameraRigKeyframe>,
23}
24
25/// Create a new camera rig export.
26#[allow(dead_code)]
27pub fn new_camera_rig_export(name: &str) -> CameraRigExport {
28    CameraRigExport {
29        name: name.to_string(),
30        keyframes: Vec::new(),
31    }
32}
33
34/// Add a keyframe.
35#[allow(dead_code)]
36pub fn rig_add_keyframe(
37    rig: &mut CameraRigExport,
38    time: f32,
39    position: [f32; 3],
40    target: [f32; 3],
41    fov: f32,
42) {
43    rig.keyframes.push(CameraRigKeyframe {
44        time,
45        position,
46        target,
47        fov,
48    });
49}
50
51/// Keyframe count.
52#[allow(dead_code)]
53pub fn rig_keyframe_count(rig: &CameraRigExport) -> usize {
54    rig.keyframes.len()
55}
56
57/// Duration (last time - first time).
58#[allow(dead_code)]
59pub fn rig_duration(rig: &CameraRigExport) -> f32 {
60    if rig.keyframes.len() < 2 {
61        return 0.0;
62    }
63    rig.keyframes.last().map_or(0.0, |k| k.time) - rig.keyframes.first().map_or(0.0, |k| k.time)
64}
65
66/// Get keyframe at index.
67#[allow(dead_code)]
68pub fn rig_keyframe_at(rig: &CameraRigExport, idx: usize) -> Option<&CameraRigKeyframe> {
69    rig.keyframes.get(idx)
70}
71
72/// Clear all keyframes.
73#[allow(dead_code)]
74pub fn rig_clear(rig: &mut CameraRigExport) {
75    rig.keyframes.clear();
76}
77
78/// Export to JSON.
79#[allow(dead_code)]
80pub fn camera_rig_to_json(rig: &CameraRigExport) -> String {
81    format!(
82        "{{\"name\":\"{}\",\"keyframes\":{},\"duration\":{:.6}}}",
83        rig.name,
84        rig.keyframes.len(),
85        rig_duration(rig),
86    )
87}
88
89/// Validate keyframes are sorted by time.
90#[allow(dead_code)]
91pub fn rig_validate(rig: &CameraRigExport) -> bool {
92    rig.keyframes.windows(2).all(|w| w[0].time <= w[1].time)
93}
94
95#[cfg(test)]
96mod tests {
97    use super::*;
98
99    #[test]
100    fn test_new() {
101        let r = new_camera_rig_export("main");
102        assert_eq!(rig_keyframe_count(&r), 0);
103    }
104
105    #[test]
106    fn test_add_keyframe() {
107        let mut r = new_camera_rig_export("cam");
108        rig_add_keyframe(&mut r, 0.0, [0.0; 3], [0.0, 0.0, -1.0], 60.0);
109        assert_eq!(rig_keyframe_count(&r), 1);
110    }
111
112    #[test]
113    fn test_duration() {
114        let mut r = new_camera_rig_export("cam");
115        rig_add_keyframe(&mut r, 0.0, [0.0; 3], [0.0; 3], 60.0);
116        rig_add_keyframe(&mut r, 2.5, [1.0; 3], [0.0; 3], 60.0);
117        assert!((rig_duration(&r) - 2.5).abs() < 1e-6);
118    }
119
120    #[test]
121    fn test_duration_single() {
122        let mut r = new_camera_rig_export("cam");
123        rig_add_keyframe(&mut r, 1.0, [0.0; 3], [0.0; 3], 60.0);
124        assert!((rig_duration(&r)).abs() < 1e-6);
125    }
126
127    #[test]
128    fn test_keyframe_at() {
129        let mut r = new_camera_rig_export("cam");
130        rig_add_keyframe(&mut r, 0.0, [1.0, 2.0, 3.0], [0.0; 3], 45.0);
131        let kf = rig_keyframe_at(&r, 0).expect("should succeed");
132        assert!((kf.fov - 45.0).abs() < 1e-6);
133    }
134
135    #[test]
136    fn test_clear() {
137        let mut r = new_camera_rig_export("cam");
138        rig_add_keyframe(&mut r, 0.0, [0.0; 3], [0.0; 3], 60.0);
139        rig_clear(&mut r);
140        assert_eq!(rig_keyframe_count(&r), 0);
141    }
142
143    #[test]
144    fn test_to_json() {
145        let r = new_camera_rig_export("test");
146        assert!(camera_rig_to_json(&r).contains("\"name\":\"test\""));
147    }
148
149    #[test]
150    fn test_validate_sorted() {
151        let mut r = new_camera_rig_export("cam");
152        rig_add_keyframe(&mut r, 0.0, [0.0; 3], [0.0; 3], 60.0);
153        rig_add_keyframe(&mut r, 1.0, [0.0; 3], [0.0; 3], 60.0);
154        assert!(rig_validate(&r));
155    }
156
157    #[test]
158    fn test_validate_unsorted() {
159        let mut r = new_camera_rig_export("cam");
160        rig_add_keyframe(&mut r, 2.0, [0.0; 3], [0.0; 3], 60.0);
161        rig_add_keyframe(&mut r, 1.0, [0.0; 3], [0.0; 3], 60.0);
162        assert!(!rig_validate(&r));
163    }
164
165    #[test]
166    fn test_keyframe_at_oob() {
167        let r = new_camera_rig_export("cam");
168        assert!(rig_keyframe_at(&r, 0).is_none());
169    }
170}