Detect diagonals better on controllers
This commit is contained in:
parent
5fd1957d74
commit
6e36254203
|
|
@ -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 gilrs::{Event as GamepadEvent, EventType, GamepadId, ev::Code};
|
||||||
use winit::{
|
use winit::{
|
||||||
|
|
@ -8,12 +11,13 @@ use winit::{
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
emulator::{EmulatorClient, EmulatorCommand, SimId, VBKey},
|
emulator::{EmulatorClient, EmulatorCommand, SimId, VBKey},
|
||||||
input::{InputMapping, MappingProvider},
|
input::{AxisMapping, InputMapping, MappingProvider},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct Controller {
|
pub struct Controller {
|
||||||
pub sim_id: SimId,
|
pub sim_id: SimId,
|
||||||
state: VBKey,
|
state: VBKey,
|
||||||
|
axis_values: HashMap<(GamepadId, Code), f32>,
|
||||||
mapping: Arc<RwLock<InputMapping>>,
|
mapping: Arc<RwLock<InputMapping>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -22,6 +26,7 @@ impl Controller {
|
||||||
Self {
|
Self {
|
||||||
sim_id,
|
sim_id,
|
||||||
state: VBKey::SGN,
|
state: VBKey::SGN,
|
||||||
|
axis_values: HashMap::new(),
|
||||||
mapping: mappings.for_sim(sim_id).clone(),
|
mapping: mappings.for_sim(sim_id).clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -45,22 +50,18 @@ impl Controller {
|
||||||
(VBKey::empty(), mappings)
|
(VBKey::empty(), mappings)
|
||||||
}
|
}
|
||||||
EventType::AxisChanged(_, value, code) => {
|
EventType::AxisChanged(_, value, code) => {
|
||||||
let (neg, pos) = self.map_axis(&event.id, &code)?;
|
let mapping = self.map_axis(&event.id, &code)?;
|
||||||
let mut pressed = VBKey::empty();
|
self.axis_values.insert((event.id, code), value);
|
||||||
let mut released = VBKey::empty();
|
|
||||||
if value < -0.75 {
|
let pair_value = mapping
|
||||||
pressed = pressed.union(neg);
|
.pair
|
||||||
}
|
.and_then(|p| self.axis_values.get(&(event.id, p)))
|
||||||
if value > 0.75 {
|
.copied()
|
||||||
pressed = pressed.union(pos);
|
.unwrap_or_default();
|
||||||
}
|
let neg = mapping.neg;
|
||||||
if value > -0.65 {
|
let pos = mapping.pos;
|
||||||
released = released.union(neg);
|
|
||||||
}
|
axis_presses(value, pair_value, neg, pos)
|
||||||
if value < 0.65 {
|
|
||||||
released = released.union(pos);
|
|
||||||
}
|
|
||||||
(pressed, released)
|
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
return None;
|
return None;
|
||||||
|
|
@ -87,7 +88,7 @@ impl Controller {
|
||||||
self.mapping.read().unwrap().map_button(id, code)
|
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<AxisMapping> {
|
||||||
self.mapping.read().unwrap().map_axis(id, code)
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
57
src/input.rs
57
src/input.rs
|
|
@ -52,12 +52,28 @@ pub trait Mappings {
|
||||||
fn use_default_mappings(&mut self);
|
fn use_default_mappings(&mut self);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Serialize, Deserialize)]
|
||||||
|
pub struct AxisMapping {
|
||||||
|
pub neg: VBKey,
|
||||||
|
pub pos: VBKey,
|
||||||
|
pub pair: Option<Code>,
|
||||||
|
}
|
||||||
|
impl Default for AxisMapping {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
neg: VBKey::empty(),
|
||||||
|
pos: VBKey::empty(),
|
||||||
|
pair: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct GamepadMapping {
|
pub struct GamepadMapping {
|
||||||
buttons: HashMap<Code, VBKey>,
|
buttons: HashMap<Code, VBKey>,
|
||||||
axes: HashMap<Code, (VBKey, VBKey)>,
|
axes: HashMap<Code, AxisMapping>,
|
||||||
default_buttons: HashMap<Code, VBKey>,
|
default_buttons: HashMap<Code, VBKey>,
|
||||||
default_axes: HashMap<Code, (VBKey, VBKey)>,
|
default_axes: HashMap<Code, AxisMapping>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GamepadMapping {
|
impl GamepadMapping {
|
||||||
|
|
@ -78,7 +94,8 @@ impl GamepadMapping {
|
||||||
let mut default_axes = HashMap::new();
|
let mut default_axes = HashMap::new();
|
||||||
let mut default_axis = |axis: Axis, neg: VBKey, pos: VBKey| {
|
let mut default_axis = |axis: Axis, neg: VBKey, pos: VBKey| {
|
||||||
if let Some(code) = gamepad.axis_code(axis) {
|
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);
|
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) {
|
pub fn add_axis_neg_mapping(&mut self, key: VBKey, code: Code) {
|
||||||
let entry = self
|
let entry = self.axes.entry(code).or_default();
|
||||||
.axes
|
entry.neg = entry.neg.union(key);
|
||||||
.entry(code)
|
|
||||||
.or_insert((VBKey::empty(), VBKey::empty()));
|
|
||||||
entry.0 = entry.0.union(key);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_axis_pos_mapping(&mut self, key: VBKey, code: Code) {
|
pub fn add_axis_pos_mapping(&mut self, key: VBKey, code: Code) {
|
||||||
let entry = self
|
let entry = self.axes.entry(code).or_default();
|
||||||
.axes
|
entry.pos = entry.pos.union(key);
|
||||||
.entry(code)
|
|
||||||
.or_insert((VBKey::empty(), VBKey::empty()));
|
|
||||||
entry.1 = entry.1.union(key);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn save_mappings(&self) -> PersistedGamepadMapping {
|
fn save_mappings(&self) -> PersistedGamepadMapping {
|
||||||
|
|
@ -145,11 +156,11 @@ impl GamepadMapping {
|
||||||
impl Mappings for GamepadMapping {
|
impl Mappings for GamepadMapping {
|
||||||
fn mapping_names(&self) -> HashMap<VBKey, Vec<String>> {
|
fn mapping_names(&self) -> HashMap<VBKey, Vec<String>> {
|
||||||
let mut results: HashMap<VBKey, Vec<String>> = HashMap::new();
|
let mut results: HashMap<VBKey, Vec<String>> = HashMap::new();
|
||||||
for (axis, (left_keys, right_keys)) in &self.axes {
|
for (axis, mapping) in &self.axes {
|
||||||
for key in left_keys.iter() {
|
for key in mapping.neg.iter() {
|
||||||
results.entry(key).or_default().push(format!("-{axis}"));
|
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}"));
|
results.entry(key).or_default().push(format!("+{axis}"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -162,10 +173,10 @@ impl Mappings for GamepadMapping {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clear_mappings(&mut self, key: VBKey) {
|
fn clear_mappings(&mut self, key: VBKey) {
|
||||||
self.axes.retain(|_, (left, right)| {
|
self.axes.retain(|_, mapping| {
|
||||||
*left = left.difference(key);
|
mapping.neg = mapping.neg.difference(key);
|
||||||
*right = right.difference(key);
|
mapping.pos = mapping.pos.difference(key);
|
||||||
!(left.is_empty() && right.is_empty())
|
!(mapping.neg.is_empty() && mapping.pos.is_empty())
|
||||||
});
|
});
|
||||||
self.buttons.retain(|_, keys| {
|
self.buttons.retain(|_, keys| {
|
||||||
*keys = keys.difference(key);
|
*keys = keys.difference(key);
|
||||||
|
|
@ -200,7 +211,7 @@ impl InputMapping {
|
||||||
mappings.buttons.get(code).copied()
|
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<AxisMapping> {
|
||||||
let mappings = self.gamepads.get(id)?.read().unwrap();
|
let mappings = self.gamepads.get(id)?.read().unwrap();
|
||||||
mappings.axes.get(code).copied()
|
mappings.axes.get(code).copied()
|
||||||
}
|
}
|
||||||
|
|
@ -459,9 +470,9 @@ struct PersistedKeyboardMapping {
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
struct PersistedGamepadMapping {
|
struct PersistedGamepadMapping {
|
||||||
buttons: Vec<(Code, VBKey)>,
|
buttons: Vec<(Code, VBKey)>,
|
||||||
axes: Vec<(Code, (VBKey, VBKey))>,
|
axes: Vec<(Code, AxisMapping)>,
|
||||||
default_buttons: Vec<(Code, VBKey)>,
|
default_buttons: Vec<(Code, VBKey)>,
|
||||||
default_axes: Vec<(Code, (VBKey, VBKey))>,
|
default_axes: Vec<(Code, AxisMapping)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
|
#[derive(Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue