diff --git a/src/controller.rs b/src/controller.rs index 6c922aa..cdc9b56 100644 --- a/src/controller.rs +++ b/src/controller.rs @@ -1,4 +1,7 @@ -use std::sync::{Arc, RwLock}; +use std::{ + collections::HashMap, + sync::{Arc, RwLock}, +}; use gilrs::{Event as GamepadEvent, EventType, GamepadId, ev::Code}; use winit::{ @@ -8,12 +11,13 @@ use winit::{ use crate::{ emulator::{EmulatorClient, EmulatorCommand, SimId, VBKey}, - input::{InputMapping, MappingProvider}, + input::{AxisMapping, InputMapping, MappingProvider}, }; pub struct Controller { pub sim_id: SimId, state: VBKey, + axis_values: HashMap<(GamepadId, Code), f32>, mapping: Arc>, } @@ -22,6 +26,7 @@ impl Controller { Self { sim_id, state: VBKey::SGN, + axis_values: HashMap::new(), mapping: mappings.for_sim(sim_id).clone(), } } @@ -45,22 +50,18 @@ impl Controller { (VBKey::empty(), mappings) } EventType::AxisChanged(_, value, code) => { - let (neg, pos) = self.map_axis(&event.id, &code)?; - let mut pressed = VBKey::empty(); - let mut released = VBKey::empty(); - if value < -0.75 { - pressed = pressed.union(neg); - } - if value > 0.75 { - pressed = pressed.union(pos); - } - if value > -0.65 { - released = released.union(neg); - } - if value < 0.65 { - released = released.union(pos); - } - (pressed, released) + let mapping = self.map_axis(&event.id, &code)?; + self.axis_values.insert((event.id, code), value); + + let pair_value = mapping + .pair + .and_then(|p| self.axis_values.get(&(event.id, p))) + .copied() + .unwrap_or_default(); + let neg = mapping.neg; + let pos = mapping.pos; + + axis_presses(value, pair_value, neg, pos) } _ => { return None; @@ -87,7 +88,7 @@ impl Controller { self.mapping.read().unwrap().map_button(id, code) } - fn map_axis(&self, id: &GamepadId, code: &Code) -> Option<(VBKey, VBKey)> { + fn map_axis(&self, id: &GamepadId, code: &Code) -> Option { self.mapping.read().unwrap().map_axis(id, code) } } @@ -126,3 +127,81 @@ impl ControllerManager { } } } + +fn axis_presses(value: f32, pair_value: f32, neg: VBKey, pos: VBKey) -> (VBKey, VBKey) { + use std::f32::consts::FRAC_PI_3; + + let mut pressed = VBKey::empty(); + let mut released = VBKey::empty(); + + let magnitude = value.hypot(pair_value); + let abs_angle = pair_value.atan2(value); + + if magnitude < 0.65 { + released = released.union(neg).union(pos); + } else if abs_angle <= FRAC_PI_3 { + // stick tilted towards positive + released = released.union(neg); + + if magnitude >= 0.75 { + pressed = pressed.union(pos); + } + } else if abs_angle >= 2.0 * FRAC_PI_3 { + // stick tilted towards negative + released = released.union(pos); + + if magnitude >= 0.75 { + pressed = pressed.union(neg); + } + } else { + released = released.union(neg).union(pos); + } + + (pressed, released) +} + +#[cfg(test)] +mod tests { + use super::{VBKey, axis_presses}; + + const NEG: VBKey = VBKey::LL; + const POS: VBKey = VBKey::LR; + + const NONE: VBKey = VBKey::empty(); + const BOTH: VBKey = NEG.union(POS); + + #[test] + fn detects_no_input() { + let (pressed, released) = axis_presses(0.0, 0.0, NEG, POS); + assert_eq!(pressed, NONE); + assert_eq!(released, BOTH); + } + + #[test] + fn detects_pos_input() { + let (pressed, released) = axis_presses(1.0, 0.0, NEG, POS); + assert_eq!(pressed, POS); + assert_eq!(released, NEG); + } + + #[test] + fn detects_neg_input() { + let (pressed, released) = axis_presses(-1.0, 0.0, NEG, POS); + assert_eq!(pressed, NEG); + assert_eq!(released, POS); + } + + #[test] + fn respects_dead_zone() { + let (pressed, released) = axis_presses(0.70, 0.0, NEG, POS); + assert_eq!(pressed, NONE); + assert_eq!(released, NEG); + } + + #[test] + fn handles_diagonals_ok() { + let (pressed, released) = axis_presses(0.6, 0.6, NEG, POS); + assert_eq!(pressed, POS); + assert_eq!(released, NEG); + } +} diff --git a/src/input.rs b/src/input.rs index 47d2b3c..20fcf2c 100644 --- a/src/input.rs +++ b/src/input.rs @@ -52,12 +52,28 @@ pub trait Mappings { fn use_default_mappings(&mut self); } +#[derive(Clone, Copy, Serialize, Deserialize)] +pub struct AxisMapping { + pub neg: VBKey, + pub pos: VBKey, + pub pair: Option, +} +impl Default for AxisMapping { + fn default() -> Self { + Self { + neg: VBKey::empty(), + pos: VBKey::empty(), + pair: None, + } + } +} + #[derive(Serialize, Deserialize)] pub struct GamepadMapping { buttons: HashMap, - axes: HashMap, + axes: HashMap, default_buttons: HashMap, - default_axes: HashMap, + default_axes: HashMap, } impl GamepadMapping { @@ -78,7 +94,8 @@ impl GamepadMapping { let mut default_axes = HashMap::new(); let mut default_axis = |axis: Axis, neg: VBKey, pos: VBKey| { if let Some(code) = gamepad.axis_code(axis) { - default_axes.insert(code, (neg, pos)); + let pair = axis.second_axis().and_then(|a| gamepad.axis_code(a)); + default_axes.insert(code, AxisMapping { neg, pos, pair }); } }; default_axis(Axis::LeftStickX, VBKey::LL, VBKey::LR); @@ -102,19 +119,13 @@ impl GamepadMapping { } pub fn add_axis_neg_mapping(&mut self, key: VBKey, code: Code) { - let entry = self - .axes - .entry(code) - .or_insert((VBKey::empty(), VBKey::empty())); - entry.0 = entry.0.union(key); + let entry = self.axes.entry(code).or_default(); + entry.neg = entry.neg.union(key); } pub fn add_axis_pos_mapping(&mut self, key: VBKey, code: Code) { - let entry = self - .axes - .entry(code) - .or_insert((VBKey::empty(), VBKey::empty())); - entry.1 = entry.1.union(key); + let entry = self.axes.entry(code).or_default(); + entry.pos = entry.pos.union(key); } fn save_mappings(&self) -> PersistedGamepadMapping { @@ -145,11 +156,11 @@ impl GamepadMapping { impl Mappings for GamepadMapping { fn mapping_names(&self) -> HashMap> { let mut results: HashMap> = HashMap::new(); - for (axis, (left_keys, right_keys)) in &self.axes { - for key in left_keys.iter() { + for (axis, mapping) in &self.axes { + for key in mapping.neg.iter() { results.entry(key).or_default().push(format!("-{axis}")); } - for key in right_keys.iter() { + for key in mapping.pos.iter() { results.entry(key).or_default().push(format!("+{axis}")); } } @@ -162,10 +173,10 @@ impl Mappings for GamepadMapping { } fn clear_mappings(&mut self, key: VBKey) { - self.axes.retain(|_, (left, right)| { - *left = left.difference(key); - *right = right.difference(key); - !(left.is_empty() && right.is_empty()) + self.axes.retain(|_, mapping| { + mapping.neg = mapping.neg.difference(key); + mapping.pos = mapping.pos.difference(key); + !(mapping.neg.is_empty() && mapping.pos.is_empty()) }); self.buttons.retain(|_, keys| { *keys = keys.difference(key); @@ -200,7 +211,7 @@ impl InputMapping { mappings.buttons.get(code).copied() } - pub fn map_axis(&self, id: &GamepadId, code: &Code) -> Option<(VBKey, VBKey)> { + pub fn map_axis(&self, id: &GamepadId, code: &Code) -> Option { let mappings = self.gamepads.get(id)?.read().unwrap(); mappings.axes.get(code).copied() } @@ -459,9 +470,9 @@ struct PersistedKeyboardMapping { #[derive(Serialize, Deserialize)] struct PersistedGamepadMapping { buttons: Vec<(Code, VBKey)>, - axes: Vec<(Code, (VBKey, VBKey))>, + axes: Vec<(Code, AxisMapping)>, default_buttons: Vec<(Code, VBKey)>, - default_axes: Vec<(Code, (VBKey, VBKey))>, + default_axes: Vec<(Code, AxisMapping)>, } #[derive(Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]