Skip to main content

repame_input/
motion.rs

1use glam::{Quat, Vec3};
2
3/// Sensor axes a motion-capable controller reports.
4#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
5pub enum SensorKind {
6    #[default]
7    None,
8    Gyroscope,
9    Accelerometer,
10    GyroscopeAccelerometer,
11}
12
13impl SensorKind {
14    pub fn has_gyroscope(self) -> bool {
15        matches!(self, Self::Gyroscope | Self::GyroscopeAccelerometer)
16    }
17
18    pub fn has_accelerometer(self) -> bool {
19        matches!(self, Self::Accelerometer | Self::GyroscopeAccelerometer)
20    }
21}
22
23/// One reading from a device's motion sensors, in the device frame.
24#[derive(Debug, Clone, Copy, PartialEq, Default)]
25pub struct MotionSample {
26    pub acceleration: Vec3,
27    pub angular_velocity: Vec3,
28}
29
30/// Platform hook that feeds sensor readings for one device.
31pub trait MotionSource {
32    fn kind(&self) -> SensorKind;
33    fn sample(&self) -> Option<MotionSample>;
34}
35
36/// Per-tick readout of a device's tracked motion.
37#[derive(Debug, Clone, Copy, PartialEq, Default)]
38pub struct MotionSnapshot {
39    pub kind: SensorKind,
40    /// Device-to-world orientation.
41    pub orientation: Quat,
42    pub angular_velocity: Vec3,
43    pub acceleration: Vec3,
44}
45
46/// Gravity magnitude used by [`MotionTracker::gravity`], in m/s^2.
47pub const GRAVITY: f32 = 9.806_65;
48
49/// Integrates gyro readings into a device orientation, as console SDKs do for
50/// motion controllers: `orientation` maps the device frame into the world,
51/// device-frame angular velocity composes on its right, and the accelerometer
52/// is reported in the device frame alongside it.
53#[derive(Debug, Clone, Copy, PartialEq)]
54pub struct MotionTracker {
55    kind: SensorKind,
56    orientation: Quat,
57    angular_velocity: Vec3,
58    acceleration: Vec3,
59}
60
61impl MotionTracker {
62    pub fn new(kind: SensorKind) -> Self {
63        Self {
64            kind,
65            orientation: Quat::IDENTITY,
66            angular_velocity: Vec3::ZERO,
67            acceleration: Vec3::ZERO,
68        }
69    }
70
71    pub fn kind(&self) -> SensorKind {
72        self.kind
73    }
74
75    /// Folds one sensor reading in. A missing axis leaves its previous value, so
76    /// gyro-only and accel-only devices both integrate cleanly.
77    pub fn integrate(&mut self, sample: MotionSample, dt_seconds: f32) {
78        if dt_seconds > 0.0 && self.kind.has_gyroscope() {
79            let spin = sample.angular_velocity * dt_seconds;
80            if spin.length_squared() > 0.0 {
81                self.orientation = (self.orientation * Quat::from_scaled_axis(spin)).normalize();
82            }
83            self.angular_velocity = sample.angular_velocity;
84        }
85        if self.kind.has_accelerometer() {
86            self.acceleration = sample.acceleration;
87        }
88    }
89
90    /// Resets orientation to identity and drops the held axes.
91    pub fn recenter(&mut self) {
92        self.orientation = Quat::IDENTITY;
93        self.angular_velocity = Vec3::ZERO;
94        self.acceleration = Vec3::ZERO;
95    }
96
97    pub fn orientation(&self) -> Quat {
98        self.orientation
99    }
100
101    pub fn angular_velocity(&self) -> Vec3 {
102        self.angular_velocity
103    }
104
105    pub fn acceleration(&self) -> Vec3 {
106        self.acceleration
107    }
108
109    /// Gravity direction in the device frame: world down through the
110    /// orientation's inverse, since `orientation` maps device into world.
111    pub fn gravity(&self) -> Vec3 {
112        self.orientation.inverse() * Vec3::new(0.0, -GRAVITY, 0.0)
113    }
114
115    pub fn snapshot(&self) -> MotionSnapshot {
116        MotionSnapshot {
117            kind: self.kind,
118            orientation: self.orientation,
119            angular_velocity: self.angular_velocity,
120            acceleration: self.acceleration,
121        }
122    }
123}