From 67fe292f190776134da672ccbd1ccd6a2bff40b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Fourrier?= Date: Wed, 28 Jul 2021 14:39:37 +0200 Subject: [PATCH] First try at sensor API implementation --- Cargo.toml | 3 + examples/sensors.rs | 89 ++++++++++++++++++++ src/sdl2/controller.rs | 77 ++++++++++++++++++ src/sdl2/event.rs | 25 ++++++ src/sdl2/lib.rs | 1 + src/sdl2/sdl.rs | 7 ++ src/sdl2/sensor.rs | 180 +++++++++++++++++++++++++++++++++++++++++ 7 files changed, 382 insertions(+) create mode 100644 examples/sensors.rs create mode 100644 src/sdl2/sensor.rs diff --git a/Cargo.toml b/Cargo.toml index 4f9f5b8c..29e18172 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -140,6 +140,9 @@ name = "renderer-yuv" required-features = ["ttf", "image"] name = "resource-manager" +[[example]] +name = "sensors" + [[example]] required-features = ["ttf"] name = "ttf-demo" diff --git a/examples/sensors.rs b/examples/sensors.rs new file mode 100644 index 00000000..c6b367db --- /dev/null +++ b/examples/sensors.rs @@ -0,0 +1,89 @@ +use std::time::{Duration, Instant}; + +use sdl2::{event::Event, sensor::SensorType}; + +extern crate sdl2; + +fn main() -> Result<(), String> { + let sdl_context = sdl2::init()?; + let game_controller_subsystem = sdl_context.game_controller()?; + + let available = game_controller_subsystem + .num_joysticks() + .map_err(|e| format!("can't enumerate joysticks: {}", e))?; + + println!("{} joysticks available", available); + + // Iterate over all available joysticks and look for game controllers. + let controller = (0..available) + .find_map(|id| { + if !game_controller_subsystem.is_game_controller(id) { + println!("{} is not a game controller", id); + return None; + } + + println!("Attempting to open controller {}", id); + + match game_controller_subsystem.open(id) { + Ok(c) => { + // We managed to find and open a game controller, + // exit the loop + println!("Success: opened \"{}\"", c.name()); + Some(c) + } + Err(e) => { + println!("failed: {:?}", e); + None + } + } + }) + .expect("Couldn't open any controller"); + + if !controller.has_sensor(SensorType::Accelerometer) { + return Err(format!( + "{} doesn't support the accelerometer", + controller.name() + )); + } + if !controller.has_sensor(SensorType::Gyroscope) { + return Err(format!( + "{} doesn't support the gyroscope", + controller.name() + )); + } + + controller + .sensor_set_enabled(SensorType::Accelerometer, true) + .map_err(|e| format!("error enabling accelerometer: {}", e))?; + controller + .sensor_set_enabled(SensorType::Gyroscope, true) + .map_err(|e| format!("error enabling gyroscope: {}", e))?; + let mut now = Instant::now(); + for event in sdl_context.event_pump()?.wait_iter() { + if false && now.elapsed() > Duration::from_secs(1) { + now = Instant::now(); + + let mut gyro_data = [0f32; 3]; + let mut accel_data = [0f32; 3]; + + controller + .sensor_get_data(SensorType::Gyroscope, &mut gyro_data) + .map_err(|e| format!("error getting gyro data: {}", e))?; + controller + .sensor_get_data(SensorType::Accelerometer, &mut accel_data) + .map_err(|e| format!("error getting accel data: {}", e))?; + + println!("gyro: {:?}, accel: {:?}", gyro_data, accel_data); + } + + if let Event::ControllerSensorUpdated { .. } = event { + println!("{:?}", event); + } + + if let Event::Quit { .. } = event { + break; + } + } + + Ok(()) +} diff --git a/src/sdl2/controller.rs b/src/sdl2/controller.rs index c8996771..6e931da1 100644 --- a/src/sdl2/controller.rs +++ b/src/sdl2/controller.rs @@ -1,10 +1,13 @@ use crate::rwops::RWops; +use crate::sensor::SensorType; use libc::c_char; +use std::convert::TryInto; use std::error; use std::ffi::{CStr, CString, NulError}; use std::fmt; use std::io; use std::path::Path; +use sys::SDL_bool; use crate::common::{validate_int, IntegerOrSdlError}; use crate::get_error; @@ -486,6 +489,80 @@ impl GameController { } } +impl GameController { + #[doc(alias = "SDL_GameControllerHasSensor")] + pub fn has_sensor(&self, sensor_type: SensorType) -> bool { + let result = unsafe { sys::SDL_GameControllerHasSensor(self.raw, sensor_type.into()) }; + + match result { + sys::SDL_bool::SDL_FALSE => false, + sys::SDL_bool::SDL_TRUE => true, + } + } + + #[doc(alias = "SDL_GameControllerIsSensorEnabled")] + pub fn sensor_enabled(&self, sensor_type: SensorType) -> bool { + let result = + unsafe { sys::SDL_GameControllerIsSensorEnabled(self.raw, sensor_type.into()) }; + + match result { + sys::SDL_bool::SDL_FALSE => false, + sys::SDL_bool::SDL_TRUE => true, + } + } + + #[doc(alias = "SDL_GameControllerHasSensor")] + pub fn sensor_set_enabled( + &self, + sensor_type: SensorType, + enabled: bool, + ) -> Result<(), IntegerOrSdlError> { + let result = unsafe { + sys::SDL_GameControllerSetSensorEnabled( + self.raw, + sensor_type.into(), + if enabled { + SDL_bool::SDL_TRUE + } else { + SDL_bool::SDL_FALSE + }, + ) + }; + + if result != 0 { + Err(IntegerOrSdlError::SdlError(get_error())) + } else { + Ok(()) + } + } + + /// Get data from a sensor. + /// + /// The number of data points depends on the sensor. Both Gyroscope and + /// Accelerometer return 3 values, one for each axis. + #[doc(alias = "SDL_GameControllerGetSensorData")] + pub fn sensor_get_data( + &self, + sensor_type: SensorType, + data: &mut [f32], + ) -> Result<(), IntegerOrSdlError> { + let result = unsafe { + sys::SDL_GameControllerGetSensorData( + self.raw, + sensor_type.into(), + data.as_mut_ptr(), + data.len().try_into().unwrap(), + ) + }; + + if result != 0 { + Err(IntegerOrSdlError::SdlError(get_error())) + } else { + Ok(()) + } + } +} + impl Drop for GameController { #[doc(alias = "SDL_GameControllerClose")] fn drop(&mut self) { diff --git a/src/sdl2/event.rs b/src/sdl2/event.rs index 2fb3919b..2569b4d9 100644 --- a/src/sdl2/event.rs +++ b/src/sdl2/event.rs @@ -298,6 +298,7 @@ pub enum EventType { ControllerDeviceAdded = SDL_EventType::SDL_CONTROLLERDEVICEADDED as u32, ControllerDeviceRemoved = SDL_EventType::SDL_CONTROLLERDEVICEREMOVED as u32, ControllerDeviceRemapped = SDL_EventType::SDL_CONTROLLERDEVICEREMAPPED as u32, + ControllerSensorUpdated = SDL_EventType::SDL_CONTROLLERSENSORUPDATE as u32, FingerDown = SDL_EventType::SDL_FINGERDOWN as u32, FingerUp = SDL_EventType::SDL_FINGERUP as u32, @@ -366,6 +367,7 @@ impl TryFrom for EventType { SDL_CONTROLLERDEVICEADDED => ControllerDeviceAdded, SDL_CONTROLLERDEVICEREMOVED => ControllerDeviceRemoved, SDL_CONTROLLERDEVICEREMAPPED => ControllerDeviceRemapped, + SDL_CONTROLLERSENSORUPDATE => ControllerSensorUpdated, SDL_FINGERDOWN => FingerDown, SDL_FINGERUP => FingerUp, @@ -674,6 +676,18 @@ pub enum Event { which: u32, }, + /// Triggered when the gyroscope or accelerometer is updated + ControllerSensorUpdated { + timestamp: u32, + which: u32, + /// The type of the sensor, see SensorType. + sensor: i32, + /// Data from the sensor. + /// + /// See the `sensor` module for more information. + data: [f32; 3], + }, + FingerDown { timestamp: u32, touch_id: i64, @@ -1612,6 +1626,15 @@ impl Event { which: event.which as u32, } } + EventType::ControllerSensorUpdated => { + let event = raw.csensor; + Event::ControllerSensorUpdated { + timestamp: event.timestamp, + which: event.which as u32, + sensor: event.sensor, + data: event.data, + } + } EventType::FingerDown => { let event = raw.tfinger; @@ -1881,6 +1904,7 @@ impl Event { | (Self::ControllerDeviceAdded { .. }, Self::ControllerDeviceAdded { .. }) | (Self::ControllerDeviceRemoved { .. }, Self::ControllerDeviceRemoved { .. }) | (Self::ControllerDeviceRemapped { .. }, Self::ControllerDeviceRemapped { .. }) + | (Self::ControllerSensorUpdated { .. }, Self::ControllerSensorUpdated { .. }) | (Self::FingerDown { .. }, Self::FingerDown { .. }) | (Self::FingerUp { .. }, Self::FingerUp { .. }) | (Self::FingerMotion { .. }, Self::FingerMotion { .. }) @@ -1947,6 +1971,7 @@ impl Event { Self::ControllerDeviceAdded { timestamp, .. } => timestamp, Self::ControllerDeviceRemoved { timestamp, .. } => timestamp, Self::ControllerDeviceRemapped { timestamp, .. } => timestamp, + Self::ControllerSensorUpdated { timestamp, .. } => timestamp, Self::FingerDown { timestamp, .. } => timestamp, Self::FingerUp { timestamp, .. } => timestamp, Self::FingerMotion { timestamp, .. } => timestamp, diff --git a/src/sdl2/lib.rs b/src/sdl2/lib.rs index ba6f5a2f..f991631e 100644 --- a/src/sdl2/lib.rs +++ b/src/sdl2/lib.rs @@ -77,6 +77,7 @@ pub mod joystick; pub mod keyboard; pub mod log; pub mod messagebox; +pub mod sensor; pub mod mouse; pub mod pixels; pub mod rect; diff --git a/src/sdl2/sdl.rs b/src/sdl2/sdl.rs index 6b43d101..39a88191 100644 --- a/src/sdl2/sdl.rs +++ b/src/sdl2/sdl.rs @@ -121,6 +121,12 @@ impl Sdl { GameControllerSubsystem::new(self) } + /// Initializes the game controller subsystem. + #[inline] + pub fn sensor(&self) -> Result { + SensorSubsystem::new(self) + } + /// Initializes the timer subsystem. #[inline] pub fn timer(&self) -> Result { @@ -278,6 +284,7 @@ subsystem!(VideoSubsystem, sys::SDL_INIT_VIDEO, nosync); subsystem!(TimerSubsystem, sys::SDL_INIT_TIMER, sync); // The event queue can be read from other threads. subsystem!(EventSubsystem, sys::SDL_INIT_EVENTS, sync); +subsystem!(SensorSubsystem, sys::SDL_INIT_SENSOR, sync); static mut IS_EVENT_PUMP_ALIVE: bool = false; diff --git a/src/sdl2/sensor.rs b/src/sdl2/sensor.rs new file mode 100644 index 00000000..8e08e4ad --- /dev/null +++ b/src/sdl2/sensor.rs @@ -0,0 +1,180 @@ +/// Access to gyroscope and accelerometer on the controller. +/// +/// Compatible controllers including Playstation, Switch and Steam controllers include a gyroscope +/// and accelerometer to get the movement in space of the device. +/// +/// Units used by SDL: +/// - Accelerometer is in m/s² +/// - Gyroscope is in radian per second +/// +/// Axis when holding the controller: +/// - -x ... +x is left ... right +/// - -y ... +y is down ... up +/// - -z ... +z is forward ... backward +/// +/// Rotations uses the standard anti-clockwise direction around the corresponding axis from above: +/// - -x ... +x is pitch towards up +/// - -y ... +y is yaw from right to left +/// - -z ... +z is roll from right to left +use crate::sys; + +use crate::common::{validate_int, IntegerOrSdlError}; +use crate::get_error; +use crate::SensorSubsystem; +use libc::c_char; +use std::ffi::CStr; +use sys::SDL_SensorGetData; +use sys::SDL_SensorType; + +impl SensorSubsystem { + /// Retrieve the total number of attached joysticks *and* controllers identified by SDL. + #[doc(alias = "SDL_NumSensors")] + pub fn num_sensors(&self) -> Result { + let result = unsafe { sys::SDL_NumSensors() }; + + if result >= 0 { + Ok(result as u32) + } else { + Err(get_error()) + } + } + + /// Attempt to open the joystick at index `joystick_index` and return it. + #[doc(alias = "SDL_SensorOpen")] + pub fn open(&self, sensor_index: u32) -> Result { + use crate::common::IntegerOrSdlError::*; + let sensor_index = validate_int(sensor_index, "sensor_index")?; + + let sensor = unsafe { sys::SDL_SensorOpen(sensor_index) }; + + if sensor.is_null() { + Err(SdlError(get_error())) + } else { + Ok(Sensor { + subsystem: self.clone(), + raw: sensor, + }) + } + } + + /// Force joystick update when not using the event loop + #[inline] + #[doc(alias = "SDL_SensorUpdate")] + pub fn update(&self) { + unsafe { sys::SDL_SensorUpdate() }; + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum SensorType { + Unknown, + Gyroscope, + Accelerometer, +} + +impl Into for SensorType { + fn into(self) -> SDL_SensorType { + match self { + SensorType::Unknown => SDL_SensorType::SDL_SENSOR_UNKNOWN, + SensorType::Gyroscope => SDL_SensorType::SDL_SENSOR_GYRO, + SensorType::Accelerometer => SDL_SensorType::SDL_SENSOR_ACCEL, + } + } +} + +/// Wrapper around the `SDL_Sensor` object +pub struct Sensor { + subsystem: SensorSubsystem, + raw: *mut sys::SDL_Sensor, +} + +impl Sensor { + #[inline] + pub const fn subsystem(&self) -> &SensorSubsystem { + &self.subsystem + } + + /// Return the name of the sensor or an empty string if no name + /// is found. + #[doc(alias = "SDL_SensorGetName")] + pub fn name(&self) -> String { + let name = unsafe { sys::SDL_SensorGetName(self.raw) }; + + c_str_to_string(name) + } + + #[doc(alias = "SDL_SensorGetInstanceID")] + pub fn instance_id(&self) -> u32 { + let result = unsafe { sys::SDL_SensorGetInstanceID(self.raw) }; + + if result < 0 { + // Should only fail if the joystick is NULL. + panic!("{}", get_error()) + } else { + result as u32 + } + } + + /// Return the type of the sensor or `Unknown` if unsupported. + #[doc(alias = "SDL_SensorGetType")] + pub fn sensor_type(&self) -> SensorType { + let result = unsafe { sys::SDL_SensorGetType(self.raw) }; + + match result { + sys::SDL_SensorType::SDL_SENSOR_INVALID => { + panic!("{}", get_error()) + } + sys::SDL_SensorType::SDL_SENSOR_UNKNOWN => SensorType::Unknown, + sys::SDL_SensorType::SDL_SENSOR_ACCEL => SensorType::Accelerometer, + sys::SDL_SensorType::SDL_SENSOR_GYRO => SensorType::Gyroscope, + } + } + + /// Get the current data from the sensor. + /// + /// Output depends on the type of the sensor. See module documentation for units and axis. + #[doc(alias = "SDL_SensorGetType")] + pub fn get_data(&self) -> Result { + let mut data = [0f32; 16]; + let result = unsafe { SDL_SensorGetData(self.raw, data.as_mut_ptr(), data.len() as i32) }; + + if result != 0 { + Err(IntegerOrSdlError::SdlError(get_error())) + } else { + Ok(match self.sensor_type() { + SensorType::Gyroscope => SensorData::Accel([data[0], data[1], data[2]]), + SensorType::Accelerometer => SensorData::Accel([data[0], data[1], data[2]]), + SensorType::Unknown => SensorData::Unknown(data), + }) + } + } +} + +#[derive(Debug, Clone, Copy)] +pub enum SensorData { + Gyro([f32; 3]), + Accel([f32; 3]), + Unknown([f32; 16]), +} + +impl Drop for Sensor { + #[doc(alias = "SDL_SensorClose")] + fn drop(&mut self) { + unsafe { sys::SDL_SensorClose(self.raw) } + } +} + +/// Convert C string `c_str` to a String. Return an empty string if +/// `c_str` is NULL. +fn c_str_to_string(c_str: *const c_char) -> String { + if c_str.is_null() { + String::new() + } else { + unsafe { + CStr::from_ptr(c_str as *const _) + .to_str() + .unwrap() + .to_owned() + } + } +}