use std::collections::HashMap;
use glam::Vec3;
use repame_input::{MotionSample, MotionSource, SensorKind};
use repose_core::input::SensorKind as ReposeSensorKind;
use repose_core::input::{GamepadEvent, GamepadId};
use repose_platform::sensor::{SensorBackend, SensorReading, create_backend};
const DEG_TO_RAD: f32 = std::f32::consts::PI / 180.0;
const G_TO_MS2: f32 = 9.806_65;
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct PadMotion {
kind: SensorKind,
sample: MotionSample,
reported: bool,
}
impl PadMotion {
pub fn reported(&self) -> bool {
self.reported
}
}
impl MotionSource for PadMotion {
fn kind(&self) -> SensorKind {
self.kind
}
fn sample(&self) -> Option<MotionSample> {
self.reported.then_some(self.sample)
}
}
pub struct MotionPoller {
backend: Option<Box<dyn SensorBackend>>,
pads: HashMap<u32, String>,
gyroscope: HashMap<u32, [f32; 3]>,
accelerometer: HashMap<u32, [f32; 3]>,
}
impl MotionPoller {
pub fn new() -> Self {
Self {
backend: create_backend().map(|b| Box::new(b) as Box<dyn SensorBackend>),
pads: HashMap::new(),
gyroscope: HashMap::new(),
accelerometer: HashMap::new(),
}
}
pub fn feed_gamepad(&mut self, events: &[GamepadEvent]) {
for event in events {
match event {
GamepadEvent::Connected { id, name, .. } => {
self.pads.insert(id.0, name.clone());
}
GamepadEvent::Disconnected { id } => {
self.pads.remove(&id.0);
self.gyroscope.remove(&id.0);
self.accelerometer.remove(&id.0);
}
_ => {}
}
}
}
pub fn poll(&mut self) {
let Some(backend) = &mut self.backend else {
return;
};
let readings = backend.poll_sensors();
self.feed_readings(readings);
}
pub fn feed_readings(&mut self, readings: Vec<SensorReading>) {
for reading in readings {
let Some(id) = self.resolve(&reading.device) else {
log::debug!(
"motion: no pad named {:?}, dropping {:?}",
reading.device,
reading.sample.kind
);
continue;
};
match reading.sample.kind {
ReposeSensorKind::Gyroscope => {
self.gyroscope.insert(id, reading.sample.data);
}
ReposeSensorKind::Accelerometer => {
self.accelerometer.insert(id, reading.sample.data);
}
}
}
}
pub fn source(&self, id: GamepadId) -> PadMotion {
let gyroscope = self.gyroscope.get(&id.0).copied();
let accelerometer = self.accelerometer.get(&id.0).copied();
let kind = match (gyroscope.is_some(), accelerometer.is_some()) {
(true, true) => SensorKind::GyroscopeAccelerometer,
(true, false) => SensorKind::Gyroscope,
(false, true) => SensorKind::Accelerometer,
(false, false) => SensorKind::None,
};
let angular_velocity = gyroscope
.map(|d| Vec3::new(d[0], d[1], d[2]) * DEG_TO_RAD)
.unwrap_or(Vec3::ZERO);
let acceleration = accelerometer
.map(|d| Vec3::new(d[0], d[1], d[2]) * G_TO_MS2)
.unwrap_or(Vec3::ZERO);
PadMotion {
kind,
sample: MotionSample {
acceleration,
angular_velocity,
},
reported: kind != SensorKind::None,
}
}
pub fn motion_ids(&self) -> Vec<GamepadId> {
let mut ids: Vec<u32> = self
.gyroscope
.keys()
.chain(self.accelerometer.keys())
.copied()
.collect();
ids.sort_unstable();
ids.dedup();
ids.into_iter().map(GamepadId).collect()
}
fn resolve(&self, device: &str) -> Option<u32> {
if let Some((id, _)) = self.pads.iter().find(|(_, name)| name.as_str() == device) {
return Some(*id);
}
let needle = repose_core::input::normalize_device_name(device);
let mut matches = self
.pads
.iter()
.filter(|(_, name)| repose_core::input::normalize_device_name(name) == needle)
.map(|(id, _)| *id);
let first = matches.next()?;
matches.next().is_none().then_some(first)
}
}
impl Default for MotionPoller {
fn default() -> Self {
Self::new()
}
}