oximedia_virtual/tracking/
camera.rs1use 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#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct CameraTrackerConfig {
16 pub update_rate: f64,
18 pub optical_tracking: bool,
20 pub imu_tracking: bool,
22 pub fusion_weight: f32,
24 pub smoothing_window: usize,
26 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45pub enum TrackingState {
46 Lost,
48 Limited,
50 Tracking,
52}
53
54pub 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 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 pub fn update(&mut self, timestamp_ns: u64) -> Result<CameraPose> {
95 let now = Instant::now();
96
97 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 let optical_pose = if let Some(detector) = &mut self.marker_detector {
110 detector.detect_pose(timestamp_ns)?
111 } else {
112 None
113 };
114
115 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 let fused_pose = self.fuse_sensors(optical_pose, imu_pose, timestamp_ns)?;
124
125 self.tracking_state = self.determine_tracking_state(&fused_pose);
127
128 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 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 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 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 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 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 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 #[must_use]
217 pub fn current_pose(&self) -> &CameraPose {
218 &self.current_pose
219 }
220
221 #[must_use]
223 pub fn tracking_state(&self) -> TrackingState {
224 self.tracking_state
225 }
226
227 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 #[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}