diff --git a/lighthouse-protocol/src/input/gamepad_axis_2d_event.rs b/lighthouse-protocol/src/input/gamepad_axis_2d_event.rs index e421258..567343e 100644 --- a/lighthouse-protocol/src/input/gamepad_axis_2d_event.rs +++ b/lighthouse-protocol/src/input/gamepad_axis_2d_event.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Serialize}; -use crate::Vec2; +use crate::{Direction, Vec2}; /// A 2D axis event on a gamepad. #[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] @@ -11,3 +11,52 @@ pub struct GamepadAxis2DEvent { /// The value of the axis (each component is between -1.0 and 1.0, modeled after the Web Gamepad API). pub value: Vec2, } + +impl GamepadAxis2DEvent { + /// The approximate direction (outside of a small deadzone). + pub fn direction(&self) -> Option { + let deadzone_radius: f64 = 0.1; + if self.value.length() < deadzone_radius { + return None; + } + + // See https://www.desmos.com/calculator/472pdoxzqa for visualization + // Note that the y-axis is flipped here, per computer graphics conventions, + // hence the sign flip (-y instead of y). + let Vec2 { x, y } = self.value; + let left_or_up = x < -y; + let right_or_up = -x < -y; + Some( + match (left_or_up, right_or_up) { + (true, true) => Direction::Up, + (true, false) => Direction::Left, + (false, true) => Direction::Right, + (false, false) => Direction::Down, + } + ) + } +} + +#[cfg(test)] +mod tests { + use crate::{Direction, Vec2, Zero}; + + use super::GamepadAxis2DEvent; + + #[test] + fn directions() { + assert_eq!(event(Vec2::UP).direction(), Some(Direction::Up)); + assert_eq!(event(Vec2::DOWN).direction(), Some(Direction::Down)); + assert_eq!(event(Vec2::LEFT).direction(), Some(Direction::Left)); + assert_eq!(event(Vec2::RIGHT).direction(), Some(Direction::Right)); + assert_eq!(event(Vec2::ZERO).direction(), None); + assert_eq!(event(Vec2::new(-0.05, 0.05)).direction(), None); // within deadzone + } + + fn event(value: Vec2) -> GamepadAxis2DEvent { + GamepadAxis2DEvent { + index: 0, + value, + } + } +} diff --git a/lighthouse-protocol/src/input/gamepad_button_event.rs b/lighthouse-protocol/src/input/gamepad_button_event.rs index daf0ac2..2f80ea9 100644 --- a/lighthouse-protocol/src/input/gamepad_button_event.rs +++ b/lighthouse-protocol/src/input/gamepad_button_event.rs @@ -1,5 +1,7 @@ use serde::{Deserialize, Serialize}; +use crate::Direction; + /// A button event on a gamepad. #[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] #[serde(tag = "control", rename_all = "camelCase")] @@ -11,3 +13,17 @@ pub struct GamepadButtonEvent { /// The value of the button (between 0.0 and 1.0, modeled after the Web Gamepad API). pub value: f64, } + +impl GamepadButtonEvent { + /// The direction if one of the D-pad buttons was pressed. + /// See https://www.w3.org/TR/gamepad/#dfn-standard-gamepad + pub fn d_pad_direction(&self) -> Option { + match self.index { + 12 => Some(Direction::Up), + 13 => Some(Direction::Down), + 14 => Some(Direction::Left), + 15 => Some(Direction::Right), + _ => None, + } + } +} diff --git a/lighthouse-protocol/src/input/input_event.rs b/lighthouse-protocol/src/input/input_event.rs index 82cd48c..7bcb15f 100644 --- a/lighthouse-protocol/src/input/input_event.rs +++ b/lighthouse-protocol/src/input/input_event.rs @@ -1,6 +1,8 @@ use serde::{Deserialize, Serialize}; -use super::{GamepadEvent, KeyEvent, MouseEvent}; +use crate::Direction; + +use super::{EventSource, GamepadControlEvent, GamepadEvent, KeyEvent, MouseEvent}; /// A user input event, as generated by the new frontend (LUNA). #[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] @@ -11,6 +13,49 @@ pub enum InputEvent { Gamepad(GamepadEvent), } +impl InputEvent { + /// The event's source. + pub fn source(&self) -> &EventSource { + match self { + InputEvent::Key(KeyEvent { source, .. }) => source, + InputEvent::Mouse(MouseEvent { source, .. }) => source, + InputEvent::Gamepad(GamepadEvent { source, .. }) => source, + } + } + + /// Parses the input event as an arbitrary direction. + pub fn direction(&self) -> Option { + self.left_direction().or_else(|| self.right_direction()) + } + + /// The direction if the input event represents a WASD key, D-pad or left stick. + /// Commonly used e.g. for movement in games. + pub fn left_direction(&self) -> Option { + match self { + InputEvent::Key(key) => key.wasd_direction(), + InputEvent::Gamepad(gamepad) => match &gamepad.control { + GamepadControlEvent::Button(button) => button.d_pad_direction(), + GamepadControlEvent::Axis2D(axis2d) if axis2d.index == 0 => axis2d.direction(), + _ => None, + }, + _ => None, + } + } + + /// The direction if the input event represents an arrow key or right stick. + /// Commonly used e.g. for camera control in games. + pub fn right_direction(&self) -> Option { + match self { + InputEvent::Key(key) => key.arrow_direction(), + InputEvent::Gamepad(gamepad) => match &gamepad.control { + GamepadControlEvent::Axis2D(axis2d) if axis2d.index == 1 => axis2d.direction(), + _ => None, + }, + _ => None, + } + } +} + #[cfg(test)] mod tests { use serde_json::json; diff --git a/lighthouse-protocol/src/input/key_event.rs b/lighthouse-protocol/src/input/key_event.rs index 4f4b445..432a087 100644 --- a/lighthouse-protocol/src/input/key_event.rs +++ b/lighthouse-protocol/src/input/key_event.rs @@ -1,5 +1,7 @@ use serde::{Deserialize, Serialize}; +use crate::Direction; + use super::{EventSource, KeyModifiers}; /// A keyboard event. @@ -17,3 +19,32 @@ pub struct KeyEvent { /// The held key modifiers. pub modifiers: KeyModifiers, } + +impl KeyEvent { + /// The direction if either the WASD or arrow keys were pressed. + pub fn direction(&self) -> Option { + self.wasd_direction().or_else(|| self.arrow_direction()) + } + + /// The direction if one of the WASD keys was pressed. + pub fn wasd_direction(&self) -> Option { + match self.code.as_str() { + "KeyW" => Some(Direction::Up), + "KeyA" => Some(Direction::Left), + "KeyS" => Some(Direction::Down), + "KeyD" => Some(Direction::Right), + _ => None, + } + } + + /// The direction if one of the arrow keys was pressed. + pub fn arrow_direction(&self) -> Option { + match self.code.as_str() { + "ArrowUp" => Some(Direction::Up), + "ArrowLeft" => Some(Direction::Left), + "ArrowDown" => Some(Direction::Down), + "ArrowRight" => Some(Direction::Right), + _ => None, + } + } +} diff --git a/lighthouse-protocol/src/utils/delta.rs b/lighthouse-protocol/src/utils/delta.rs deleted file mode 100644 index d2cdc77..0000000 --- a/lighthouse-protocol/src/utils/delta.rs +++ /dev/null @@ -1,71 +0,0 @@ -use std::{fmt, ops::{Add, Sub, Neg}}; - -use rand::{Rng, thread_rng}; - -use crate::Rotation; - -/// A 2D vector on the lighthouse display. -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] -pub struct Delta { - pub dx: i32, - pub dy: i32, -} - -impl Delta { - /// The empty delta. - pub const ZERO: Self = Self::new(0, 0); - - /// The vector pointing one pixel to the left. - pub const LEFT: Self = Self::new(-1, 0); - /// The vector pointing one pixel up. - pub const UP: Self = Self::new( 0, -1); - /// The vector pointing one pixel to the right. - pub const RIGHT: Self = Self::new( 1, 0); - /// The vector pointing one pixel down. - pub const DOWN: Self = Self::new( 0, 1); - - /// Creates a new vector. - pub const fn new(dx: i32, dy: i32) -> Self { - Self { dx, dy } - } - - /// Randomly one of the four cardinal directions with the given rng. - pub fn random_cardinal_with(rng: &mut impl Rng) -> Self { - Rotation::random_cardinal_with(rng) * Self::RIGHT - } - - /// Randomly one of the four cardinal directions with the thread-local rng. - pub fn random_cardinal() -> Self { - Self::random_cardinal_with(&mut thread_rng()) - } -} - -impl fmt::Display for Delta { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "({}, {})", self.dx, self.dy) - } -} - -impl Add for Delta { - type Output = Delta; - - fn add(self, rhs: Self) -> Self { - Self::new(self.dx + rhs.dx, self.dy + rhs.dy) - } -} - -impl Sub for Delta { - type Output = Delta; - - fn sub(self, rhs: Self) -> Self { - Self::new(self.dx - rhs.dx, self.dy - rhs.dy) - } -} - -impl Neg for Delta { - type Output = Delta; - - fn neg(self) -> Self { - Self::new(-self.dx, -self.dy) - } -} diff --git a/lighthouse-protocol/src/utils/direction.rs b/lighthouse-protocol/src/utils/direction.rs new file mode 100644 index 0000000..82072c1 --- /dev/null +++ b/lighthouse-protocol/src/utils/direction.rs @@ -0,0 +1,56 @@ +use std::fmt::Debug; + +use rand::{prelude::Distribution, distributions::Standard}; +use serde::{Deserialize, Serialize}; + +use super::{Delta, Unity, Zero}; + +/// One of the four cardinal directions. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub enum Direction { + Up, + Down, + Left, + Right, +} + +impl TryFrom> for Direction where T: Zero + Unity + PartialEq + Debug { + type Error = String; + + fn try_from(delta: Delta) -> Result { + if delta == Delta::UP { + Ok(Direction::Up) + } else if delta == Delta::DOWN { + Ok(Direction::Down) + } else if delta == Delta::LEFT { + Ok(Direction::Left) + } else if delta == Delta::RIGHT { + Ok(Direction::Right) + } else { + Err(format!("Not a direction: {:?}", delta)) + } + } +} + +impl From for Delta where T: Zero + Unity { + fn from(direction: Direction) -> Self { + match direction { + Direction::Up => Delta::UP, + Direction::Down => Delta::DOWN, + Direction::Left => Delta::LEFT, + Direction::Right => Delta::RIGHT, + } + } +} + +impl Distribution for Standard { + fn sample(&self, rng: &mut R) -> Direction { + match rng.gen_range(0..4) { + 0 => Direction::Up, + 1 => Direction::Down, + 2 => Direction::Left, + 3 => Direction::Right, + _ => unreachable!(), + } + } +} diff --git a/lighthouse-protocol/src/utils/mod.rs b/lighthouse-protocol/src/utils/mod.rs index c637d92..822bd4d 100644 --- a/lighthouse-protocol/src/utils/mod.rs +++ b/lighthouse-protocol/src/utils/mod.rs @@ -1,15 +1,19 @@ mod color; +mod direction; mod rect; mod rem_euclid; mod rotation; +mod sqrt; mod unity; mod vec2; mod zero; pub use color::*; +pub use direction::*; pub use rect::*; pub use rem_euclid::*; pub use rotation::*; +pub use sqrt::*; pub use unity::*; pub use vec2::*; pub use zero::*; diff --git a/lighthouse-protocol/src/utils/sqrt.rs b/lighthouse-protocol/src/utils/sqrt.rs new file mode 100644 index 0000000..a940ef0 --- /dev/null +++ b/lighthouse-protocol/src/utils/sqrt.rs @@ -0,0 +1,19 @@ +/// A type whose values have a square root. +pub trait Sqrt { + /// The square root. + fn sqrt(self) -> Self; +} + +macro_rules! impl_sqrt { + ($($tys:ty),*) => { + $(impl Sqrt for $tys { + fn sqrt(self) -> Self { + <$tys>::sqrt(self) + } + })* + }; +} + +impl_sqrt!( + f32, f64 +); diff --git a/lighthouse-protocol/src/utils/vec2.rs b/lighthouse-protocol/src/utils/vec2.rs index eb516c6..e22c818 100644 --- a/lighthouse-protocol/src/utils/vec2.rs +++ b/lighthouse-protocol/src/utils/vec2.rs @@ -1,9 +1,9 @@ -use std::{fmt, ops::{Add, AddAssign, Neg, Sub, SubAssign}}; +use std::{fmt, ops::{Add, AddAssign, Mul, Neg, Sub, SubAssign}}; use rand::{thread_rng, Rng}; use serde::{Deserialize, Serialize}; -use super::{Unity, Zero}; +use super::{Sqrt, Unity, Zero}; /// A 2D vector. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] @@ -59,6 +59,13 @@ impl Vec2 where T: Zero + Unity { } } +impl Vec2 where T: Add + Mul + Sqrt + Copy { + /// The vector's length. + pub fn length(&self) -> T { + (self.x * self.x + self.y * self.y).sqrt() + } +} + impl fmt::Display for Vec2 where T: fmt::Display { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "({}, {})", self.x, self.y)