oxihuman_export/
camera_stereo_export.rs1#![allow(dead_code)]
4
5use std::f32::consts::PI;
8
9#[allow(dead_code)]
10#[derive(Debug, Clone, Copy, PartialEq)]
11pub enum StereoMode {
12 SideBySide,
13 Anaglyph,
14 TopBottom,
15}
16
17#[allow(dead_code)]
18#[derive(Debug, Clone)]
19pub struct CameraStereoExport {
20 pub mode: StereoMode,
21 pub eye_separation: f32,
22 pub convergence_distance: f32,
23 pub fov_degrees: f32,
24}
25
26#[allow(dead_code)]
27pub fn default_stereo_camera() -> CameraStereoExport {
28 CameraStereoExport {
29 mode: StereoMode::SideBySide,
30 eye_separation: 0.065,
31 convergence_distance: 2.0,
32 fov_degrees: 60.0,
33 }
34}
35
36#[allow(dead_code)]
37pub fn stereo_fov_radians(cam: &CameraStereoExport) -> f32 {
38 cam.fov_degrees * PI / 180.0
39}
40
41#[allow(dead_code)]
42pub fn stereo_left_offset(cam: &CameraStereoExport) -> [f32; 3] {
43 [-cam.eye_separation * 0.5, 0.0, 0.0]
44}
45
46#[allow(dead_code)]
47pub fn stereo_right_offset(cam: &CameraStereoExport) -> [f32; 3] {
48 [cam.eye_separation * 0.5, 0.0, 0.0]
49}
50
51#[allow(dead_code)]
52pub fn stereo_mode_name(cam: &CameraStereoExport) -> &'static str {
53 match cam.mode {
54 StereoMode::SideBySide => "side_by_side",
55 StereoMode::Anaglyph => "anaglyph",
56 StereoMode::TopBottom => "top_bottom",
57 }
58}
59
60#[allow(dead_code)]
61pub fn validate_stereo(cam: &CameraStereoExport) -> bool {
62 cam.eye_separation > 0.0
63 && cam.convergence_distance > 0.0
64 && (1.0..=180.0).contains(&cam.fov_degrees)
65}
66
67#[allow(dead_code)]
68pub fn camera_stereo_to_json(cam: &CameraStereoExport) -> String {
69 format!(
70 "{{\"mode\":\"{}\",\"eye_sep\":{},\"fov\":{}}}",
71 stereo_mode_name(cam),
72 cam.eye_separation,
73 cam.fov_degrees,
74 )
75}
76
77#[allow(dead_code)]
78pub fn parallax_angle_deg(cam: &CameraStereoExport) -> f32 {
79 if cam.convergence_distance == 0.0 {
80 return 0.0;
81 }
82 let half_sep = cam.eye_separation * 0.5;
83 (half_sep / cam.convergence_distance).atan() * 180.0 / PI
84}
85
86#[cfg(test)]
87mod tests {
88 use super::*;
89
90 #[test]
91 fn test_default_camera() {
92 let cam = default_stereo_camera();
93 assert!(cam.eye_separation > 0.0);
94 }
95
96 #[test]
97 fn test_fov_radians() {
98 let cam = default_stereo_camera();
99 let r = stereo_fov_radians(&cam);
100 assert!((r - PI / 3.0).abs() < 1e-4);
101 }
102
103 #[test]
104 fn test_left_offset_negative() {
105 let cam = default_stereo_camera();
106 let off = stereo_left_offset(&cam);
107 assert!(off[0] < 0.0);
108 }
109
110 #[test]
111 fn test_right_offset_positive() {
112 let cam = default_stereo_camera();
113 let off = stereo_right_offset(&cam);
114 assert!(off[0] > 0.0);
115 }
116
117 #[test]
118 fn test_mode_name() {
119 let cam = default_stereo_camera();
120 assert_eq!(stereo_mode_name(&cam), "side_by_side");
121 }
122
123 #[test]
124 fn test_validate_default() {
125 let cam = default_stereo_camera();
126 assert!(validate_stereo(&cam));
127 }
128
129 #[test]
130 fn test_json_output() {
131 let cam = default_stereo_camera();
132 let j = camera_stereo_to_json(&cam);
133 assert!(j.contains("fov"));
134 }
135
136 #[test]
137 fn test_parallax_angle_positive() {
138 let cam = default_stereo_camera();
139 let angle = parallax_angle_deg(&cam);
140 assert!(angle > 0.0);
141 }
142
143 #[test]
144 fn test_anaglyph_mode() {
145 let mut cam = default_stereo_camera();
146 cam.mode = StereoMode::Anaglyph;
147 assert_eq!(stereo_mode_name(&cam), "anaglyph");
148 }
149
150 #[test]
151 fn test_invalid_fov() {
152 let mut cam = default_stereo_camera();
153 cam.fov_degrees = 0.0;
154 assert!(!validate_stereo(&cam));
155 }
156}