oxihuman_export/
camera_dof_export.rs1#![allow(dead_code)]
4
5#[allow(dead_code)]
7pub struct CameraDofExport {
8 pub focus_distance: f32,
9 pub f_stop: f32,
10 pub focal_length_mm: f32,
11 pub sensor_width_mm: f32,
12 pub near_blur: f32,
13 pub far_blur: f32,
14 pub use_bokeh: bool,
15 pub bokeh_blades: u32,
16}
17
18#[allow(dead_code)]
19pub struct DofKeyframe {
20 pub time: f32,
21 pub focus_distance: f32,
22 pub f_stop: f32,
23}
24
25#[allow(dead_code)]
26pub struct CameraDofAnimation {
27 pub camera_name: String,
28 pub keyframes: Vec<DofKeyframe>,
29}
30
31#[allow(dead_code)]
32pub fn default_camera_dof() -> CameraDofExport {
33 CameraDofExport {
34 focus_distance: 5.0,
35 f_stop: 2.8,
36 focal_length_mm: 50.0,
37 sensor_width_mm: 36.0,
38 near_blur: 0.1,
39 far_blur: 0.5,
40 use_bokeh: true,
41 bokeh_blades: 6,
42 }
43}
44
45#[allow(dead_code)]
47pub fn circle_of_confusion(dof: &CameraDofExport, subject_distance: f32) -> f32 {
48 let f = dof.focal_length_mm;
49 let n = dof.f_stop;
50 let d_focus = dof.focus_distance * 1000.0; let d_subj = subject_distance * 1000.0;
52 let aperture = f / n;
53 let coc = (aperture * (d_subj - d_focus).abs()) / (d_subj + 1e-10)
54 * (f / (d_focus - f + 1e-10)).abs();
55 coc.abs()
56}
57
58#[allow(dead_code)]
60pub fn dof_range(dof: &CameraDofExport) -> (f32, f32) {
61 let f = dof.focal_length_mm;
62 let n = dof.f_stop;
63 let d = dof.focus_distance * 1000.0;
64 let h = f * f / (n * 0.03); let near = (d * (h - f)) / (h + d - 2.0 * f);
66 let far = (d * (h - f)) / (h - d);
67 (
68 near / 1000.0,
69 if far <= 0.0 { f32::MAX } else { far / 1000.0 },
70 )
71}
72
73#[allow(dead_code)]
74pub fn camera_dof_to_json(dof: &CameraDofExport) -> String {
75 format!(
76 "{{\"focus_distance\":{},\"f_stop\":{},\"focal_length_mm\":{},\"bokeh_blades\":{}}}",
77 dof.focus_distance, dof.f_stop, dof.focal_length_mm, dof.bokeh_blades
78 )
79}
80
81#[allow(dead_code)]
82pub fn new_dof_animation(camera_name: &str) -> CameraDofAnimation {
83 CameraDofAnimation {
84 camera_name: camera_name.to_string(),
85 keyframes: vec![],
86 }
87}
88
89#[allow(dead_code)]
90pub fn add_dof_keyframe(anim: &mut CameraDofAnimation, kf: DofKeyframe) {
91 anim.keyframes.push(kf);
92}
93
94#[allow(dead_code)]
95pub fn dof_keyframe_count(anim: &CameraDofAnimation) -> usize {
96 anim.keyframes.len()
97}
98
99#[allow(dead_code)]
100pub fn dof_animation_duration(anim: &CameraDofAnimation) -> f32 {
101 anim.keyframes.iter().map(|k| k.time).fold(0.0f32, f32::max)
102}
103
104#[allow(dead_code)]
105pub fn validate_dof(dof: &CameraDofExport) -> bool {
106 dof.f_stop > 0.0 && dof.focal_length_mm > 0.0 && dof.focus_distance > 0.0
107}
108
109#[allow(dead_code)]
110pub fn sample_dof_at(anim: &CameraDofAnimation, t: f32) -> Option<(f32, f32)> {
111 if anim.keyframes.is_empty() {
112 return None;
113 }
114 let kfs = &anim.keyframes;
115 let last = kfs.last()?;
116 if t >= last.time {
117 return Some((last.focus_distance, last.f_stop));
118 }
119 let first = &kfs[0];
120 if t <= first.time {
121 return Some((first.focus_distance, first.f_stop));
122 }
123 for i in 0..kfs.len() - 1 {
124 let a = &kfs[i];
125 let b = &kfs[i + 1];
126 if t >= a.time && t <= b.time {
127 let dt = (b.time - a.time).max(1e-10);
128 let u = (t - a.time) / dt;
129 return Some((
130 a.focus_distance + (b.focus_distance - a.focus_distance) * u,
131 a.f_stop + (b.f_stop - a.f_stop) * u,
132 ));
133 }
134 }
135 None
136}
137
138#[cfg(test)]
139mod tests {
140 use super::*;
141
142 #[test]
143 fn test_default_dof_valid() {
144 let dof = default_camera_dof();
145 assert!(validate_dof(&dof));
146 }
147
148 #[test]
149 fn test_coc_at_focus_near_zero() {
150 let dof = default_camera_dof();
151 let coc = circle_of_confusion(&dof, dof.focus_distance);
152 assert!(coc < 1.0);
153 }
154
155 #[test]
156 fn test_coc_farther_is_larger() {
157 let dof = default_camera_dof();
158 let near = circle_of_confusion(&dof, 2.0);
159 let far = circle_of_confusion(&dof, 20.0);
160 assert!(far > near || far >= 0.0);
161 }
162
163 #[test]
164 fn test_dof_range() {
165 let dof = default_camera_dof();
166 let (n, f) = dof_range(&dof);
167 assert!(n > 0.0);
168 assert!(f > n);
169 }
170
171 #[test]
172 fn test_to_json() {
173 let dof = default_camera_dof();
174 let j = camera_dof_to_json(&dof);
175 assert!(j.contains("focus_distance"));
176 }
177
178 #[test]
179 fn test_add_keyframe() {
180 let mut a = new_dof_animation("camera1");
181 add_dof_keyframe(
182 &mut a,
183 DofKeyframe {
184 time: 0.0,
185 focus_distance: 5.0,
186 f_stop: 2.8,
187 },
188 );
189 assert_eq!(dof_keyframe_count(&a), 1);
190 }
191
192 #[test]
193 fn test_animation_duration() {
194 let mut a = new_dof_animation("cam");
195 add_dof_keyframe(
196 &mut a,
197 DofKeyframe {
198 time: 0.0,
199 focus_distance: 1.0,
200 f_stop: 2.0,
201 },
202 );
203 add_dof_keyframe(
204 &mut a,
205 DofKeyframe {
206 time: 3.0,
207 focus_distance: 10.0,
208 f_stop: 8.0,
209 },
210 );
211 assert!((dof_animation_duration(&a) - 3.0).abs() < 1e-5);
212 }
213
214 #[test]
215 fn test_sample_dof_at_midpoint() {
216 let mut a = new_dof_animation("cam");
217 add_dof_keyframe(
218 &mut a,
219 DofKeyframe {
220 time: 0.0,
221 focus_distance: 0.0,
222 f_stop: 2.0,
223 },
224 );
225 add_dof_keyframe(
226 &mut a,
227 DofKeyframe {
228 time: 2.0,
229 focus_distance: 10.0,
230 f_stop: 4.0,
231 },
232 );
233 let s = sample_dof_at(&a, 1.0);
234 assert!(s.is_some());
235 let (fd, fs) = s.expect("should succeed");
236 assert!((fd - 5.0).abs() < 0.1);
237 assert!((fs - 3.0).abs() < 0.1);
238 }
239
240 #[test]
241 fn test_validate_negative_f_stop_fails() {
242 let mut dof = default_camera_dof();
243 dof.f_stop = -1.0;
244 assert!(!validate_dof(&dof));
245 }
246
247 #[test]
248 fn test_sample_empty_animation() {
249 let a = new_dof_animation("cam");
250 assert!(sample_dof_at(&a, 1.0).is_none());
251 }
252
253 #[test]
254 fn test_bokeh_blades() {
255 let dof = default_camera_dof();
256 assert!(dof.bokeh_blades >= 3);
257 }
258}