Skip to main content

voirs_spatial/platforms/
generic.rs

1//! Generic Platform Integration
2//!
3//! This module provides a generic platform implementation that can be used
4//! as a fallback when no specific VR/AR platform is available, or for testing.
5
6use crate::platforms::{
7    DeviceInfo, EyeTrackingData, HandTrackingData, PlatformCapabilities, PlatformIntegration,
8    PlatformTrackingData, PoseData, TrackingConfig, TrackingQuality, TrackingState,
9};
10use crate::position::{PlatformData, PlatformType};
11use crate::types::Position3D;
12use crate::{Error, Result};
13use async_trait::async_trait;
14use std::collections::HashMap;
15use tokio::time::Instant;
16
17/// Generic platform integration for testing and fallback scenarios
18pub struct GenericPlatform {
19    device_info: DeviceInfo,
20    capabilities: PlatformCapabilities,
21    tracking_active: bool,
22    config: TrackingConfig,
23
24    // Generic platform state
25    simulation_mode: SimulationMode,
26    motion_pattern: MotionPattern,
27    quality_degradation: f32,
28
29    // Timing and state
30    start_time: Option<Instant>,
31    last_update: Option<Instant>,
32}
33
34/// Different simulation modes for the generic platform
35#[derive(Debug, Clone, Copy, PartialEq)]
36pub enum SimulationMode {
37    /// Static pose (no movement)
38    Static,
39    /// Simple circular motion
40    Circular,
41    /// Random walk pattern
42    RandomWalk,
43    /// Figure-8 motion pattern
44    Figure8,
45    /// Realistic head movement simulation
46    Realistic,
47}
48
49/// Motion pattern parameters
50#[derive(Debug, Clone)]
51pub struct MotionPattern {
52    /// Movement speed multiplier
53    pub speed: f32,
54    /// Movement amplitude
55    pub amplitude: f32,
56    /// Base position offset
57    pub base_position: Position3D,
58    /// Rotation speed multiplier
59    pub rotation_speed: f32,
60    /// Add noise to movement
61    pub add_noise: bool,
62}
63
64impl Default for MotionPattern {
65    fn default() -> Self {
66        Self {
67            speed: 1.0,
68            amplitude: 1.0,
69            base_position: Position3D::new(0.0, 1.7, 0.0),
70            rotation_speed: 1.0,
71            add_noise: true,
72        }
73    }
74}
75
76impl GenericPlatform {
77    /// Create new generic platform integration
78    pub fn new() -> Self {
79        Self {
80            device_info: DeviceInfo {
81                name: "Generic Platform".to_string(),
82                manufacturer: "VoiRS".to_string(),
83                model: "Generic VR/AR Device".to_string(),
84                serial_number: "SIM-000001".to_string(),
85                firmware_version: "1.0.0".to_string(),
86                platform_version: "Generic 1.0".to_string(),
87            },
88            capabilities: PlatformCapabilities {
89                head_tracking_6dof: true,
90                hand_tracking: false,
91                eye_tracking: false,
92                controller_tracking: false,
93                room_scale: false,
94                passthrough: false,
95                refresh_rates: vec![60.0],
96                tracking_range: 5.0,
97            },
98            tracking_active: false,
99            config: TrackingConfig::default(),
100            simulation_mode: SimulationMode::Realistic,
101            motion_pattern: MotionPattern::default(),
102            quality_degradation: 0.0,
103            start_time: None,
104            last_update: None,
105        }
106    }
107
108    /// Create a generic platform with specific simulation mode
109    pub fn with_simulation_mode(simulation_mode: SimulationMode) -> Self {
110        let mut platform = Self::new();
111        platform.simulation_mode = simulation_mode;
112        platform
113    }
114
115    /// Configure the motion pattern
116    pub fn configure_motion(&mut self, pattern: MotionPattern) {
117        self.motion_pattern = pattern;
118    }
119
120    /// Set quality degradation factor (0.0 = perfect, 1.0 = completely degraded)
121    pub fn set_quality_degradation(&mut self, degradation: f32) {
122        self.quality_degradation = degradation.clamp(0.0, 1.0);
123    }
124
125    /// Generate simulated tracking data based on current simulation mode
126    fn generate_tracking_data(&self) -> PlatformTrackingData {
127        let now = Instant::now();
128        let elapsed = if let Some(start_time) = self.start_time {
129            now.duration_since(start_time).as_secs_f32()
130        } else {
131            0.0
132        };
133
134        let head_pose = self.calculate_head_pose(elapsed);
135        let quality = self.calculate_tracking_quality(elapsed);
136
137        PlatformTrackingData {
138            head_pose,
139            left_controller: None, // Generic platform doesn't simulate controllers
140            right_controller: None,
141            quality: quality.clone(),
142            timestamp: now,
143            raw_data: PlatformData {
144                device_id: "Generic".to_string(),
145                pose_data: vec![],
146                tracking_confidence: quality.overall_quality,
147                platform_timestamp: (elapsed * 1000.0) as u64,
148                properties: {
149                    let mut props = HashMap::new();
150                    props.insert(
151                        "simulation_mode".to_string(),
152                        format!("{:?}", self.simulation_mode),
153                    );
154                    props.insert("elapsed_time".to_string(), elapsed.to_string());
155                    props.insert(
156                        "quality_degradation".to_string(),
157                        self.quality_degradation.to_string(),
158                    );
159                    props
160                },
161            },
162        }
163    }
164
165    /// Calculate head pose based on simulation mode and time
166    fn calculate_head_pose(&self, time: f32) -> PoseData {
167        let position = match self.simulation_mode {
168            SimulationMode::Static => self.motion_pattern.base_position,
169            SimulationMode::Circular => self.calculate_circular_motion(time),
170            SimulationMode::RandomWalk => self.calculate_random_walk(time),
171            SimulationMode::Figure8 => self.calculate_figure8_motion(time),
172            SimulationMode::Realistic => self.calculate_realistic_motion(time),
173        };
174
175        let orientation = self.calculate_orientation(time);
176        let (linear_velocity, angular_velocity) = self.calculate_velocities(time);
177
178        PoseData {
179            position,
180            orientation,
181            linear_velocity,
182            angular_velocity,
183            confidence: 1.0 - self.quality_degradation,
184        }
185    }
186
187    /// Calculate circular motion pattern
188    fn calculate_circular_motion(&self, time: f32) -> Position3D {
189        let t = time * self.motion_pattern.speed;
190        let radius = self.motion_pattern.amplitude;
191
192        Position3D::new(
193            self.motion_pattern.base_position.x + radius * t.cos(),
194            self.motion_pattern.base_position.y,
195            self.motion_pattern.base_position.z + radius * t.sin(),
196        )
197    }
198
199    /// Calculate random walk pattern
200    fn calculate_random_walk(&self, time: f32) -> Position3D {
201        // Use deterministic "randomness" based on time for reproducibility
202        let seed = (time * 100.0) as u32;
203        let noise_x = ((seed * 1299827) % 1000) as f32 / 1000.0 - 0.5;
204        let noise_z = ((seed * 1399831) % 1000) as f32 / 1000.0 - 0.5;
205
206        Position3D::new(
207            self.motion_pattern.base_position.x + noise_x * self.motion_pattern.amplitude * 0.1,
208            self.motion_pattern.base_position.y,
209            self.motion_pattern.base_position.z + noise_z * self.motion_pattern.amplitude * 0.1,
210        )
211    }
212
213    /// Calculate figure-8 motion pattern
214    fn calculate_figure8_motion(&self, time: f32) -> Position3D {
215        let t = time * self.motion_pattern.speed * 0.5;
216        let scale = self.motion_pattern.amplitude;
217
218        Position3D::new(
219            self.motion_pattern.base_position.x + scale * (2.0 * t).sin(),
220            self.motion_pattern.base_position.y,
221            self.motion_pattern.base_position.z + scale * t.sin() * t.cos(),
222        )
223    }
224
225    /// Calculate realistic head movement pattern
226    fn calculate_realistic_motion(&self, time: f32) -> Position3D {
227        // Combine multiple frequencies to simulate natural head movement
228        let slow_wave = (time * 0.1 * self.motion_pattern.speed).sin() * 0.05;
229        let medium_wave = (time * 0.3 * self.motion_pattern.speed).sin() * 0.02;
230        let fast_wave = (time * 0.8 * self.motion_pattern.speed).sin() * 0.01;
231
232        let x_offset = (slow_wave + medium_wave) * self.motion_pattern.amplitude;
233        let y_offset = (medium_wave + fast_wave) * self.motion_pattern.amplitude * 0.5;
234        let z_offset = slow_wave * self.motion_pattern.amplitude * 0.3;
235
236        // Add noise if enabled
237        let (noise_x, noise_y, noise_z) = if self.motion_pattern.add_noise {
238            let seed = (time * 1000.0) as u32;
239            (
240                ((seed * 1299827) % 1000) as f32 / 10000.0 - 0.05,
241                ((seed * 1399831) % 1000) as f32 / 10000.0 - 0.05,
242                ((seed * 1499833) % 1000) as f32 / 10000.0 - 0.05,
243            )
244        } else {
245            (0.0, 0.0, 0.0)
246        };
247
248        Position3D::new(
249            self.motion_pattern.base_position.x + x_offset + noise_x,
250            self.motion_pattern.base_position.y + y_offset + noise_y,
251            self.motion_pattern.base_position.z + z_offset + noise_z,
252        )
253    }
254
255    /// Calculate orientation based on time and motion pattern
256    fn calculate_orientation(&self, time: f32) -> (f32, f32, f32, f32) {
257        let t = time * self.motion_pattern.rotation_speed;
258
259        // Simulate natural head rotation patterns
260        let yaw = match self.simulation_mode {
261            SimulationMode::Static => 0.0,
262            SimulationMode::Circular => t * 0.5, // Look in direction of movement
263            SimulationMode::RandomWalk => (t * 0.1).sin() * 0.3,
264            SimulationMode::Figure8 => (t * 0.3).sin() * 0.4,
265            SimulationMode::Realistic => (t * 0.05).sin() * 0.2 + (t * 0.15).sin() * 0.1,
266        };
267
268        let pitch = match self.simulation_mode {
269            SimulationMode::Static => 0.0,
270            _ => (t * 0.07).sin() * 0.1, // Small pitch movements
271        };
272
273        let roll = match self.simulation_mode {
274            SimulationMode::Static => 0.0,
275            _ => (t * 0.12).sin() * 0.05, // Very small roll movements
276        };
277
278        // Convert Euler angles to quaternion
279        let half_yaw = yaw * 0.5;
280        let half_pitch = pitch * 0.5;
281        let half_roll = roll * 0.5;
282
283        let cos_yaw = half_yaw.cos();
284        let sin_yaw = half_yaw.sin();
285        let cos_pitch = half_pitch.cos();
286        let sin_pitch = half_pitch.sin();
287        let cos_roll = half_roll.cos();
288        let sin_roll = half_roll.sin();
289
290        (
291            sin_roll * cos_pitch * cos_yaw - cos_roll * sin_pitch * sin_yaw,
292            cos_roll * sin_pitch * cos_yaw + sin_roll * cos_pitch * sin_yaw,
293            cos_roll * cos_pitch * sin_yaw - sin_roll * sin_pitch * cos_yaw,
294            cos_roll * cos_pitch * cos_yaw + sin_roll * sin_pitch * sin_yaw,
295        )
296    }
297
298    /// Calculate linear and angular velocities
299    fn calculate_velocities(&self, time: f32) -> (Position3D, Position3D) {
300        // Simple velocity estimation based on simulation mode
301        let linear_speed = match self.simulation_mode {
302            SimulationMode::Static => 0.0,
303            SimulationMode::Circular => self.motion_pattern.speed * self.motion_pattern.amplitude,
304            SimulationMode::RandomWalk => 0.1,
305            SimulationMode::Figure8 => {
306                self.motion_pattern.speed * self.motion_pattern.amplitude * 0.8
307            }
308            SimulationMode::Realistic => 0.05,
309        };
310
311        let angular_speed = self.motion_pattern.rotation_speed * 0.1;
312
313        (
314            Position3D::new(linear_speed * 0.1, 0.0, linear_speed * 0.1),
315            Position3D::new(angular_speed * 0.05, angular_speed, angular_speed * 0.02),
316        )
317    }
318
319    /// Calculate tracking quality with degradation over time
320    fn calculate_tracking_quality(&self, time: f32) -> TrackingQuality {
321        let base_quality = 0.75; // Generic platform has decent but not perfect quality
322
323        // Simulate quality variations
324        let time_degradation = if self.quality_degradation > 0.0 {
325            (time * 0.01).sin() * 0.1 * self.quality_degradation
326        } else {
327            0.0
328        };
329
330        let quality_variation = (time * 0.2).sin() * 0.05; // Small natural variations
331        let final_quality = (base_quality - time_degradation + quality_variation).clamp(0.1, 1.0);
332
333        let state = if final_quality > 0.8 {
334            TrackingState::Full
335        } else if final_quality > 0.5 {
336            TrackingState::Limited
337        } else if final_quality > 0.2 {
338            TrackingState::Lost
339        } else {
340            TrackingState::NotTracking
341        };
342
343        TrackingQuality {
344            overall_quality: final_quality,
345            position_quality: final_quality * 0.98,
346            orientation_quality: final_quality * 1.02,
347            feature_count: ((final_quality * 50.0) as u32).max(5),
348            state,
349        }
350    }
351}
352
353#[async_trait]
354impl PlatformIntegration for GenericPlatform {
355    async fn initialize(&mut self) -> Result<()> {
356        self.start_time = Some(Instant::now());
357        self.last_update = Some(Instant::now());
358        tracing::info!(
359            "Generic platform initialized with simulation mode: {:?}",
360            self.simulation_mode
361        );
362        Ok(())
363    }
364
365    async fn get_tracking_data(&self) -> Result<PlatformTrackingData> {
366        if !self.tracking_active {
367            return Err(Error::LegacyProcessing(
368                "Generic tracking not active".to_string(),
369            ));
370        }
371
372        Ok(self.generate_tracking_data())
373    }
374
375    async fn is_available(&self) -> bool {
376        true // Generic platform is always available
377    }
378
379    fn get_capabilities(&self) -> PlatformCapabilities {
380        self.capabilities.clone()
381    }
382
383    async fn configure_tracking(&mut self, config: TrackingConfig) -> Result<()> {
384        self.config = config;
385        tracing::info!("Configured generic tracking with config: {:?}", self.config);
386        Ok(())
387    }
388
389    fn get_device_info(&self) -> DeviceInfo {
390        self.device_info.clone()
391    }
392
393    async fn start_tracking(&mut self) -> Result<()> {
394        if self.start_time.is_none() {
395            return Err(Error::LegacyProcessing(
396                "Generic platform not initialized".to_string(),
397            ));
398        }
399
400        self.tracking_active = true;
401        self.last_update = Some(Instant::now());
402        tracing::info!("Started generic tracking");
403        Ok(())
404    }
405
406    async fn stop_tracking(&mut self) -> Result<()> {
407        self.tracking_active = false;
408        tracing::info!("Stopped generic tracking");
409        Ok(())
410    }
411
412    async fn get_hand_tracking(&self) -> Result<Option<HandTrackingData>> {
413        // Generic platform doesn't support hand tracking by default
414        Ok(None)
415    }
416
417    async fn get_eye_tracking(&self) -> Result<Option<EyeTrackingData>> {
418        // Generic platform doesn't support eye tracking by default
419        Ok(None)
420    }
421}
422
423impl Default for GenericPlatform {
424    fn default() -> Self {
425        Self::new()
426    }
427}
428
429#[cfg(test)]
430mod tests {
431    use super::*;
432
433    #[tokio::test]
434    async fn test_generic_platform_creation() {
435        let platform = GenericPlatform::new();
436        assert!(!platform.tracking_active);
437        assert_eq!(platform.simulation_mode, SimulationMode::Realistic);
438        assert_eq!(platform.device_info.manufacturer, "VoiRS");
439    }
440
441    #[tokio::test]
442    async fn test_generic_platform_always_available() {
443        let platform = GenericPlatform::new();
444        assert!(platform.is_available().await);
445    }
446
447    #[tokio::test]
448    async fn test_generic_platform_initialization() {
449        let mut platform = GenericPlatform::new();
450        assert!(platform.initialize().await.is_ok());
451        assert!(platform.start_time.is_some());
452        assert!(platform.last_update.is_some());
453    }
454
455    #[tokio::test]
456    async fn test_simulation_modes() {
457        for mode in [
458            SimulationMode::Static,
459            SimulationMode::Circular,
460            SimulationMode::RandomWalk,
461            SimulationMode::Figure8,
462            SimulationMode::Realistic,
463        ] {
464            let mut platform = GenericPlatform::with_simulation_mode(mode);
465            assert!(platform.initialize().await.is_ok());
466            assert!(platform.start_tracking().await.is_ok());
467
468            let tracking_data = platform.get_tracking_data().await;
469            assert!(tracking_data.is_ok());
470
471            let data = tracking_data.unwrap();
472            assert!(data.quality.overall_quality > 0.0);
473
474            // Static mode should have minimal movement
475            if mode == SimulationMode::Static {
476                assert_eq!(
477                    data.head_pose.position,
478                    platform.motion_pattern.base_position
479                );
480            }
481        }
482    }
483
484    #[tokio::test]
485    async fn test_motion_pattern_configuration() {
486        let mut platform = GenericPlatform::new();
487        let custom_pattern = MotionPattern {
488            speed: 2.0,
489            amplitude: 0.5,
490            base_position: Position3D::new(1.0, 2.0, 3.0),
491            rotation_speed: 0.5,
492            add_noise: false,
493        };
494
495        platform.configure_motion(custom_pattern.clone());
496        assert_eq!(platform.motion_pattern.speed, 2.0);
497        assert_eq!(platform.motion_pattern.amplitude, 0.5);
498        assert_eq!(platform.motion_pattern.base_position.x, 1.0);
499    }
500
501    #[tokio::test]
502    async fn test_quality_degradation() {
503        let mut platform = GenericPlatform::new();
504        assert!(platform.initialize().await.is_ok());
505        assert!(platform.start_tracking().await.is_ok());
506
507        // Test normal quality
508        let normal_data = platform.get_tracking_data().await.unwrap();
509        let normal_quality = normal_data.quality.overall_quality;
510
511        // Test degraded quality
512        platform.set_quality_degradation(0.5);
513        let degraded_data = platform.get_tracking_data().await.unwrap();
514        let degraded_quality = degraded_data.quality.overall_quality;
515
516        // Degraded quality should be lower or the same (allow for edge cases with random variations)
517        // The key test is that quality degradation was set correctly
518        assert!(platform.quality_degradation > 0.0);
519        assert!(degraded_quality <= normal_quality + 0.1); // Allow small tolerance for random variations
520    }
521
522    #[tokio::test]
523    async fn test_tracking_properties() {
524        let mut platform = GenericPlatform::with_simulation_mode(SimulationMode::Figure8);
525        platform.set_quality_degradation(0.2);
526        assert!(platform.initialize().await.is_ok());
527        assert!(platform.start_tracking().await.is_ok());
528
529        let tracking_data = platform.get_tracking_data().await.unwrap();
530        let props = &tracking_data.raw_data.properties;
531
532        assert_eq!(props.get("simulation_mode").unwrap(), "Figure8");
533        assert_eq!(props.get("quality_degradation").unwrap(), "0.2");
534        assert!(props.contains_key("elapsed_time"));
535    }
536
537    #[tokio::test]
538    async fn test_pose_calculation_deterministic() {
539        let platform = GenericPlatform::with_simulation_mode(SimulationMode::Circular);
540
541        // Same time should produce same pose
542        let pose1 = platform.calculate_head_pose(5.0);
543        let pose2 = platform.calculate_head_pose(5.0);
544
545        assert_eq!(pose1.position.x, pose2.position.x);
546        assert_eq!(pose1.position.y, pose2.position.y);
547        assert_eq!(pose1.position.z, pose2.position.z);
548    }
549
550    #[tokio::test]
551    async fn test_capabilities() {
552        let platform = GenericPlatform::new();
553        let capabilities = platform.get_capabilities();
554
555        assert!(capabilities.head_tracking_6dof);
556        assert!(!capabilities.hand_tracking);
557        assert!(!capabilities.eye_tracking);
558        assert!(!capabilities.controller_tracking);
559        assert_eq!(capabilities.refresh_rates, vec![60.0]);
560    }
561}