oxihuman_export/
camera_clip_export.rs1#![allow(dead_code)]
4
5#[allow(dead_code)]
9#[derive(Debug, Clone)]
10pub struct CameraClipExport {
11 pub near: f32,
12 pub far: f32,
13 pub fov_y_rad: f32,
14 pub aspect: f32,
15}
16
17#[allow(dead_code)]
19#[derive(Debug, Clone)]
20pub struct ClipKeyframe {
21 pub time: f32,
22 pub near: f32,
23 pub far: f32,
24}
25
26#[allow(dead_code)]
28#[derive(Debug, Clone)]
29pub struct CameraClipAnimation {
30 pub keyframes: Vec<ClipKeyframe>,
31}
32
33#[allow(dead_code)]
35pub fn default_camera_clip() -> CameraClipExport {
36 use std::f32::consts::FRAC_PI_4;
37 CameraClipExport {
38 near: 0.01,
39 far: 1000.0,
40 fov_y_rad: FRAC_PI_4,
41 aspect: 16.0 / 9.0,
42 }
43}
44
45#[allow(dead_code)]
47pub fn clip_range(cam: &CameraClipExport) -> f32 {
48 cam.far - cam.near
49}
50
51#[allow(dead_code)]
53pub fn validate_clip(cam: &CameraClipExport) -> bool {
54 cam.near > 0.0 && cam.far > cam.near && cam.aspect > 0.0
55}
56
57#[allow(dead_code)]
59pub fn new_clip_animation() -> CameraClipAnimation {
60 CameraClipAnimation {
61 keyframes: Vec::new(),
62 }
63}
64
65#[allow(dead_code)]
67pub fn add_clip_keyframe(anim: &mut CameraClipAnimation, time: f32, near: f32, far: f32) {
68 anim.keyframes.push(ClipKeyframe { time, near, far });
69}
70
71#[allow(dead_code)]
73pub fn clip_keyframe_count(anim: &CameraClipAnimation) -> usize {
74 anim.keyframes.len()
75}
76
77#[allow(dead_code)]
79pub fn clip_animation_duration(anim: &CameraClipAnimation) -> f32 {
80 anim.keyframes
81 .iter()
82 .map(|k| k.time)
83 .fold(0.0_f32, f32::max)
84}
85
86#[allow(dead_code)]
88pub fn sample_clip_at(anim: &CameraClipAnimation, t: f32) -> Option<(f32, f32)> {
89 if anim.keyframes.is_empty() {
90 return None;
91 }
92 let kf = &anim.keyframes;
93 if t <= kf[0].time {
94 return Some((kf[0].near, kf[0].far));
95 }
96 if t >= kf[kf.len() - 1].time {
97 let last = &kf[kf.len() - 1];
98 return Some((last.near, last.far));
99 }
100 for i in 0..kf.len() - 1 {
101 let a = &kf[i];
102 let b = &kf[i + 1];
103 if t >= a.time && t <= b.time {
104 let dt = b.time - a.time;
105 let alpha = if dt < 1e-12 { 0.0 } else { (t - a.time) / dt };
106 return Some((
107 a.near + alpha * (b.near - a.near),
108 a.far + alpha * (b.far - a.far),
109 ));
110 }
111 }
112 None
113}
114
115#[allow(dead_code)]
117pub fn camera_clip_to_json(cam: &CameraClipExport) -> String {
118 format!(
119 "{{\"near\":{:.6},\"far\":{:.6},\"fov_y\":{:.6},\"aspect\":{:.6}}}",
120 cam.near, cam.far, cam.fov_y_rad, cam.aspect
121 )
122}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127 use std::f32::consts::FRAC_PI_4;
128
129 #[test]
130 fn test_default_camera_clip() {
131 let cam = default_camera_clip();
132 assert!(validate_clip(&cam));
133 }
134
135 #[test]
136 fn test_clip_range() {
137 let cam = CameraClipExport {
138 near: 0.1,
139 far: 100.0,
140 fov_y_rad: FRAC_PI_4,
141 aspect: 1.0,
142 };
143 assert!((clip_range(&cam) - 99.9).abs() < 1e-3);
144 }
145
146 #[test]
147 fn test_validate_clip_invalid() {
148 let cam = CameraClipExport {
149 near: -1.0,
150 far: 10.0,
151 fov_y_rad: FRAC_PI_4,
152 aspect: 1.0,
153 };
154 assert!(!validate_clip(&cam));
155 }
156
157 #[test]
158 fn test_add_clip_keyframe() {
159 let mut anim = new_clip_animation();
160 add_clip_keyframe(&mut anim, 0.0, 0.01, 100.0);
161 assert_eq!(clip_keyframe_count(&anim), 1);
162 }
163
164 #[test]
165 fn test_clip_animation_duration() {
166 let mut anim = new_clip_animation();
167 add_clip_keyframe(&mut anim, 0.0, 0.01, 100.0);
168 add_clip_keyframe(&mut anim, 2.0, 0.01, 200.0);
169 assert!((clip_animation_duration(&anim) - 2.0).abs() < 1e-6);
170 }
171
172 #[test]
173 fn test_sample_clip_empty() {
174 let anim = new_clip_animation();
175 assert!(sample_clip_at(&anim, 1.0).is_none());
176 }
177
178 #[test]
179 fn test_sample_clip_at_midpoint() {
180 let mut anim = new_clip_animation();
181 add_clip_keyframe(&mut anim, 0.0, 0.1, 100.0);
182 add_clip_keyframe(&mut anim, 2.0, 0.1, 200.0);
183 let (_, far) = sample_clip_at(&anim, 1.0).expect("should succeed");
184 assert!((far - 150.0).abs() < 1e-3);
185 }
186
187 #[test]
188 fn test_camera_clip_to_json() {
189 let cam = default_camera_clip();
190 let j = camera_clip_to_json(&cam);
191 assert!(j.contains("\"near\":"));
192 }
193
194 #[test]
195 fn test_fov_in_range() {
196 let cam = default_camera_clip();
197 assert!((0.0..=std::f32::consts::PI).contains(&cam.fov_y_rad));
198 }
199
200 #[test]
201 fn test_clip_range_positive() {
202 let cam = default_camera_clip();
203 assert!(clip_range(&cam) > 0.0);
204 }
205}