Skip to main content

oxihuman_export/
camera_stereo_export.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! Stereo camera rig export (left/right eye separation).
6
7use 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}