Skip to main content

oximedia_virtual/tracking/
camera.rs

1//! Camera position and rotation tracking
2//!
3//! Provides real-time 6DOF (six degrees of freedom) camera tracking
4//! using optical markers, IMU sensors, and sensor fusion.
5
6use super::{imu::ImuSensor, markers::MarkerDetector, CameraPose};
7use crate::math::{Point3, UnitQuaternion, Vector3};
8use crate::{Result, VirtualProductionError};
9use serde::{Deserialize, Serialize};
10use std::collections::VecDeque;
11use std::time::Instant;
12
13/// Camera tracker configuration
14#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct CameraTrackerConfig {
16    /// Update rate in Hz
17    pub update_rate: f64,
18    /// Enable optical tracking
19    pub optical_tracking: bool,
20    /// Enable IMU tracking
21    pub imu_tracking: bool,
22    /// Sensor fusion weight (0.0 = IMU only, 1.0 = optical only)
23    pub fusion_weight: f32,
24    /// Position smoothing window
25    pub smoothing_window: usize,
26    /// Maximum tracking latency in milliseconds
27    pub max_latency_ms: f64,
28}
29
30impl Default for CameraTrackerConfig {
31    fn default() -> Self {
32        Self {
33            update_rate: 120.0,
34            optical_tracking: true,
35            imu_tracking: true,
36            fusion_weight: 0.7,
37            smoothing_window: 5,
38            max_latency_ms: 60_000.0,
39        }
40    }
41}
42
43/// Camera tracking state
44#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45pub enum TrackingState {
46    /// No tracking data available
47    Lost,
48    /// Tracking with reduced confidence
49    Limited,
50    /// Full tracking available
51    Tracking,
52}
53
54/// Camera tracker
55pub struct CameraTracker {
56    config: CameraTrackerConfig,
57    marker_detector: Option<MarkerDetector>,
58    imu_sensor: Option<ImuSensor>,
59    current_pose: CameraPose,
60    pose_history: VecDeque<CameraPose>,
61    tracking_state: TrackingState,
62    last_update: Option<Instant>,
63}
64
65impl CameraTracker {
66    /// Create new camera tracker
67    pub fn new(config: CameraTrackerConfig) -> Result<Self> {
68        let marker_detector = if config.optical_tracking {
69            Some(MarkerDetector::new(Default::default())?)
70        } else {
71            None
72        };
73
74        let imu_sensor = if config.imu_tracking {
75            Some(ImuSensor::new(Default::default())?)
76        } else {
77            None
78        };
79
80        let pose_history = VecDeque::with_capacity(config.smoothing_window);
81
82        Ok(Self {
83            config,
84            marker_detector,
85            imu_sensor,
86            current_pose: CameraPose::default(),
87            pose_history,
88            tracking_state: TrackingState::Lost,
89            last_update: None,
90        })
91    }
92
93    /// Update camera tracking
94    pub fn update(&mut self, timestamp_ns: u64) -> Result<CameraPose> {
95        let now = Instant::now();
96
97        // Check latency
98        if let Some(last) = self.last_update {
99            let latency = now.duration_since(last);
100            if latency.as_secs_f64() * 1000.0 > self.config.max_latency_ms {
101                self.tracking_state = TrackingState::Lost;
102                return Err(VirtualProductionError::CameraTracking(format!(
103                    "Tracking latency exceeded: {latency:?}"
104                )));
105            }
106        }
107
108        // Get optical tracking data
109        let optical_pose = if let Some(detector) = &mut self.marker_detector {
110            detector.detect_pose(timestamp_ns)?
111        } else {
112            None
113        };
114
115        // Get IMU tracking data
116        let imu_pose = if let Some(imu) = &mut self.imu_sensor {
117            Some(imu.get_pose(timestamp_ns)?)
118        } else {
119            None
120        };
121
122        // Sensor fusion
123        let fused_pose = self.fuse_sensors(optical_pose, imu_pose, timestamp_ns)?;
124
125        // Update tracking state
126        self.tracking_state = self.determine_tracking_state(&fused_pose);
127
128        // Smooth pose
129        self.pose_history.push_back(fused_pose);
130        if self.pose_history.len() > self.config.smoothing_window {
131            self.pose_history.pop_front();
132        }
133
134        self.current_pose = self.smooth_pose();
135        self.last_update = Some(now);
136
137        Ok(self.current_pose)
138    }
139
140    /// Fuse optical and IMU sensor data
141    fn fuse_sensors(
142        &self,
143        optical: Option<CameraPose>,
144        imu: Option<CameraPose>,
145        timestamp_ns: u64,
146    ) -> Result<CameraPose> {
147        match (optical, imu) {
148            (Some(opt), Some(imu_pose)) => {
149                // Weighted fusion
150                let weight = f64::from(self.config.fusion_weight);
151                let position = opt.position * weight + imu_pose.position.coords() * (1.0 - weight);
152                let orientation = opt.orientation.slerp(&imu_pose.orientation, 1.0 - weight);
153                let confidence = opt.confidence * self.config.fusion_weight
154                    + imu_pose.confidence * (1.0 - self.config.fusion_weight);
155
156                Ok(CameraPose {
157                    position: Point3::from(position),
158                    orientation,
159                    timestamp_ns,
160                    confidence,
161                })
162            }
163            (Some(opt), None) => Ok(opt),
164            (None, Some(imu_pose)) => Ok(imu_pose),
165            (None, None) => {
166                // Use last known pose with reduced confidence
167                let mut pose = self.current_pose;
168                pose.confidence *= 0.5;
169                pose.timestamp_ns = timestamp_ns;
170                Ok(pose)
171            }
172        }
173    }
174
175    /// Smooth pose using history
176    fn smooth_pose(&self) -> CameraPose {
177        if self.pose_history.is_empty() {
178            return self.current_pose;
179        }
180
181        let n = self.pose_history.len() as f64;
182        let mut avg_position = Vector3::zeros();
183        let mut avg_orientation = UnitQuaternion::identity();
184        let mut avg_confidence = 0.0;
185
186        for pose in &self.pose_history {
187            avg_position += pose.position.coords() / n;
188            avg_confidence += pose.confidence / n as f32;
189        }
190
191        // Use most recent orientation (SLERP doesn't average well)
192        if let Some(last) = self.pose_history.back() {
193            avg_orientation = last.orientation;
194        }
195
196        CameraPose {
197            position: Point3::from(avg_position),
198            orientation: avg_orientation,
199            timestamp_ns: self.current_pose.timestamp_ns,
200            confidence: avg_confidence,
201        }
202    }
203
204    /// Determine tracking state based on pose quality
205    fn determine_tracking_state(&self, pose: &CameraPose) -> TrackingState {
206        if pose.confidence > 0.8 {
207            TrackingState::Tracking
208        } else if pose.confidence > 0.3 {
209            TrackingState::Limited
210        } else {
211            TrackingState::Lost
212        }
213    }
214
215    /// Get current camera pose
216    #[must_use]
217    pub fn current_pose(&self) -> &CameraPose {
218        &self.current_pose
219    }
220
221    /// Get tracking state
222    #[must_use]
223    pub fn tracking_state(&self) -> TrackingState {
224        self.tracking_state
225    }
226
227    /// Reset tracking
228    pub fn reset(&mut self) {
229        self.current_pose = CameraPose::default();
230        self.pose_history.clear();
231        self.tracking_state = TrackingState::Lost;
232        self.last_update = None;
233    }
234
235    /// Get configuration
236    #[must_use]
237    pub fn config(&self) -> &CameraTrackerConfig {
238        &self.config
239    }
240}
241
242#[cfg(test)]
243mod tests {
244    use super::*;
245
246    #[test]
247    fn test_camera_tracker_creation() {
248        let config = CameraTrackerConfig::default();
249        let tracker = CameraTracker::new(config);
250        assert!(tracker.is_ok());
251    }
252
253    #[test]
254    fn test_camera_tracker_state() {
255        let config = CameraTrackerConfig::default();
256        let tracker = CameraTracker::new(config).expect("should succeed in test");
257        assert_eq!(tracker.tracking_state(), TrackingState::Lost);
258    }
259
260    #[test]
261    fn test_camera_tracker_reset() {
262        let config = CameraTrackerConfig::default();
263        let mut tracker = CameraTracker::new(config).expect("should succeed in test");
264        tracker.reset();
265        assert_eq!(tracker.tracking_state(), TrackingState::Lost);
266    }
267
268    #[test]
269    fn test_tracking_state_determination() {
270        let config = CameraTrackerConfig::default();
271        let tracker = CameraTracker::new(config).expect("should succeed in test");
272
273        let mut pose = CameraPose::default();
274        pose.confidence = 0.9;
275        assert_eq!(
276            tracker.determine_tracking_state(&pose),
277            TrackingState::Tracking
278        );
279
280        pose.confidence = 0.5;
281        assert_eq!(
282            tracker.determine_tracking_state(&pose),
283            TrackingState::Limited
284        );
285
286        pose.confidence = 0.1;
287        assert_eq!(tracker.determine_tracking_state(&pose), TrackingState::Lost);
288    }
289}