Scale input to account for deadzones (#17015)
# Objective Fixes #3450 ## Solution Scale the input to account for the range ## Testing Updated unit tests ## Migration Guide `GamepadButtonChangedEvent.value` is now linearly rescaled to be from `0.0..=1.0` (instead of `low..=high`) and `GamepadAxisChangedEvent.value` is now linearly rescaled to be from `-1.0..=0.0`/`0.0..=1.0` (accounting for the deadzone).
This commit is contained in:
parent
120b733ab5
commit
43db44ca3a
@ -1,5 +1,7 @@
|
|||||||
//! The gamepad input functionality.
|
//! The gamepad input functionality.
|
||||||
|
|
||||||
|
use core::ops::RangeInclusive;
|
||||||
|
|
||||||
use crate::{Axis, ButtonInput, ButtonState};
|
use crate::{Axis, ButtonInput, ButtonState};
|
||||||
use alloc::string::String;
|
use alloc::string::String;
|
||||||
#[cfg(feature = "bevy_reflect")]
|
#[cfg(feature = "bevy_reflect")]
|
||||||
@ -30,7 +32,7 @@ use thiserror::Error;
|
|||||||
/// [`GamepadButtonChangedEvent`] and [`GamepadAxisChangedEvent`] when
|
/// [`GamepadButtonChangedEvent`] and [`GamepadAxisChangedEvent`] when
|
||||||
/// the in-frame relative ordering of events is important.
|
/// the in-frame relative ordering of events is important.
|
||||||
///
|
///
|
||||||
/// This event is produced by `bevy_input`
|
/// This event is produced by `bevy_input`.
|
||||||
#[derive(Event, Debug, Clone, PartialEq, From)]
|
#[derive(Event, Debug, Clone, PartialEq, From)]
|
||||||
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, PartialEq))]
|
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, PartialEq))]
|
||||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||||
@ -70,7 +72,7 @@ pub enum RawGamepadEvent {
|
|||||||
Axis(RawGamepadAxisChangedEvent),
|
Axis(RawGamepadAxisChangedEvent),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// [`GamepadButton`] changed event unfiltered by [`GamepadSettings`]
|
/// [`GamepadButton`] changed event unfiltered by [`GamepadSettings`].
|
||||||
#[derive(Event, Debug, Copy, Clone, PartialEq)]
|
#[derive(Event, Debug, Copy, Clone, PartialEq)]
|
||||||
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, PartialEq))]
|
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, PartialEq))]
|
||||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||||
@ -98,7 +100,7 @@ impl RawGamepadButtonChangedEvent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// [`GamepadAxis`] changed event unfiltered by [`GamepadSettings`]
|
/// [`GamepadAxis`] changed event unfiltered by [`GamepadSettings`].
|
||||||
#[derive(Event, Debug, Copy, Clone, PartialEq)]
|
#[derive(Event, Debug, Copy, Clone, PartialEq)]
|
||||||
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, PartialEq))]
|
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, PartialEq))]
|
||||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||||
@ -151,18 +153,18 @@ impl GamepadConnectionEvent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Is the gamepad connected?
|
/// Whether the gamepad is connected.
|
||||||
pub fn connected(&self) -> bool {
|
pub fn connected(&self) -> bool {
|
||||||
matches!(self.connection, GamepadConnection::Connected { .. })
|
matches!(self.connection, GamepadConnection::Connected { .. })
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Is the gamepad disconnected?
|
/// Whether the gamepad is disconnected.
|
||||||
pub fn disconnected(&self) -> bool {
|
pub fn disconnected(&self) -> bool {
|
||||||
!self.connected()
|
!self.connected()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// [`GamepadButton`] event triggered by a digital state change
|
/// [`GamepadButton`] event triggered by a digital state change.
|
||||||
#[derive(Event, Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Event, Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, PartialEq))]
|
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, PartialEq))]
|
||||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||||
@ -180,7 +182,7 @@ pub struct GamepadButtonStateChangedEvent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl GamepadButtonStateChangedEvent {
|
impl GamepadButtonStateChangedEvent {
|
||||||
/// Creates a new [`GamepadButtonStateChangedEvent`]
|
/// Creates a new [`GamepadButtonStateChangedEvent`].
|
||||||
pub fn new(entity: Entity, button: GamepadButton, state: ButtonState) -> Self {
|
pub fn new(entity: Entity, button: GamepadButton, state: ButtonState) -> Self {
|
||||||
Self {
|
Self {
|
||||||
entity,
|
entity,
|
||||||
@ -190,7 +192,7 @@ impl GamepadButtonStateChangedEvent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// [`GamepadButton`] event triggered by an analog state change
|
/// [`GamepadButton`] event triggered by an analog state change.
|
||||||
#[derive(Event, Debug, Clone, Copy, PartialEq)]
|
#[derive(Event, Debug, Clone, Copy, PartialEq)]
|
||||||
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, PartialEq))]
|
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, PartialEq))]
|
||||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||||
@ -205,12 +207,12 @@ pub struct GamepadButtonChangedEvent {
|
|||||||
pub button: GamepadButton,
|
pub button: GamepadButton,
|
||||||
/// The pressed state of the button.
|
/// The pressed state of the button.
|
||||||
pub state: ButtonState,
|
pub state: ButtonState,
|
||||||
/// The analog value of the button.
|
/// The analog value of the button (rescaled to be in the 0.0..=1.0 range).
|
||||||
pub value: f32,
|
pub value: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GamepadButtonChangedEvent {
|
impl GamepadButtonChangedEvent {
|
||||||
/// Creates a new [`GamepadButtonChangedEvent`]
|
/// Creates a new [`GamepadButtonChangedEvent`].
|
||||||
pub fn new(entity: Entity, button: GamepadButton, state: ButtonState, value: f32) -> Self {
|
pub fn new(entity: Entity, button: GamepadButton, state: ButtonState, value: f32) -> Self {
|
||||||
Self {
|
Self {
|
||||||
entity,
|
entity,
|
||||||
@ -221,7 +223,7 @@ impl GamepadButtonChangedEvent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// [`GamepadAxis`] event triggered by an analog state change
|
/// [`GamepadAxis`] event triggered by an analog state change.
|
||||||
#[derive(Event, Debug, Clone, Copy, PartialEq)]
|
#[derive(Event, Debug, Clone, Copy, PartialEq)]
|
||||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||||
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, PartialEq))]
|
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, PartialEq))]
|
||||||
@ -234,12 +236,12 @@ pub struct GamepadAxisChangedEvent {
|
|||||||
pub entity: Entity,
|
pub entity: Entity,
|
||||||
/// The gamepad axis assigned to the event.
|
/// The gamepad axis assigned to the event.
|
||||||
pub axis: GamepadAxis,
|
pub axis: GamepadAxis,
|
||||||
/// The value of this axis.
|
/// The value of this axis (rescaled to account for axis settings).
|
||||||
pub value: f32,
|
pub value: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GamepadAxisChangedEvent {
|
impl GamepadAxisChangedEvent {
|
||||||
/// Creates a new [`GamepadAxisChangedEvent`]
|
/// Creates a new [`GamepadAxisChangedEvent`].
|
||||||
pub fn new(entity: Entity, axis: GamepadAxis, value: f32) -> Self {
|
pub fn new(entity: Entity, axis: GamepadAxis, value: f32) -> Self {
|
||||||
Self {
|
Self {
|
||||||
entity,
|
entity,
|
||||||
@ -339,12 +341,10 @@ pub struct Gamepad {
|
|||||||
/// The USB vendor ID as assigned by the USB-IF, if available.
|
/// The USB vendor ID as assigned by the USB-IF, if available.
|
||||||
pub(crate) vendor_id: Option<u16>,
|
pub(crate) vendor_id: Option<u16>,
|
||||||
|
|
||||||
/// The USB product ID as assigned by the [vendor], if available.
|
/// The USB product ID as assigned by the [vendor][Self::vendor_id], if available.
|
||||||
///
|
|
||||||
/// [vendor]: Self::vendor_id
|
|
||||||
pub(crate) product_id: Option<u16>,
|
pub(crate) product_id: Option<u16>,
|
||||||
|
|
||||||
/// [`ButtonInput`] of [`GamepadButton`] representing their digital state
|
/// [`ButtonInput`] of [`GamepadButton`] representing their digital state.
|
||||||
pub(crate) digital: ButtonInput<GamepadButton>,
|
pub(crate) digital: ButtonInput<GamepadButton>,
|
||||||
|
|
||||||
/// [`Axis`] of [`GamepadButton`] representing their analog state.
|
/// [`Axis`] of [`GamepadButton`] representing their analog state.
|
||||||
@ -378,7 +378,7 @@ impl Gamepad {
|
|||||||
self.analog.get_unclamped(input.into())
|
self.analog.get_unclamped(input.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the left stick as a [`Vec2`]
|
/// Returns the left stick as a [`Vec2`].
|
||||||
pub fn left_stick(&self) -> Vec2 {
|
pub fn left_stick(&self) -> Vec2 {
|
||||||
Vec2 {
|
Vec2 {
|
||||||
x: self.get(GamepadAxis::LeftStickX).unwrap_or(0.0),
|
x: self.get(GamepadAxis::LeftStickX).unwrap_or(0.0),
|
||||||
@ -386,7 +386,7 @@ impl Gamepad {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the right stick as a [`Vec2`]
|
/// Returns the right stick as a [`Vec2`].
|
||||||
pub fn right_stick(&self) -> Vec2 {
|
pub fn right_stick(&self) -> Vec2 {
|
||||||
Vec2 {
|
Vec2 {
|
||||||
x: self.get(GamepadAxis::RightStickX).unwrap_or(0.0),
|
x: self.get(GamepadAxis::RightStickX).unwrap_or(0.0),
|
||||||
@ -394,7 +394,7 @@ impl Gamepad {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the directional pad as a [`Vec2`]
|
/// Returns the directional pad as a [`Vec2`].
|
||||||
pub fn dpad(&self) -> Vec2 {
|
pub fn dpad(&self) -> Vec2 {
|
||||||
Vec2 {
|
Vec2 {
|
||||||
x: self.get(GamepadButton::DPadRight).unwrap_or(0.0)
|
x: self.get(GamepadButton::DPadRight).unwrap_or(0.0)
|
||||||
@ -480,14 +480,12 @@ impl Gamepad {
|
|||||||
self.digital.get_just_released()
|
self.digital.get_just_released()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns an iterator over all analog [axes].
|
/// Returns an iterator over all analog [axes][GamepadInput].
|
||||||
///
|
|
||||||
/// [axes]: GamepadInput
|
|
||||||
pub fn get_analog_axes(&self) -> impl Iterator<Item = &GamepadInput> {
|
pub fn get_analog_axes(&self) -> impl Iterator<Item = &GamepadInput> {
|
||||||
self.analog.all_axes()
|
self.analog.all_axes()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// [`ButtonInput`] of [`GamepadButton`] representing their digital state
|
/// [`ButtonInput`] of [`GamepadButton`] representing their digital state.
|
||||||
pub fn digital(&self) -> &ButtonInput<GamepadButton> {
|
pub fn digital(&self) -> &ButtonInput<GamepadButton> {
|
||||||
&self.digital
|
&self.digital
|
||||||
}
|
}
|
||||||
@ -531,7 +529,7 @@ impl Default for Gamepad {
|
|||||||
///
|
///
|
||||||
/// ## Usage
|
/// ## Usage
|
||||||
///
|
///
|
||||||
/// This is used to determine which button has changed its value when receiving gamepad button events
|
/// This is used to determine which button has changed its value when receiving gamepad button events.
|
||||||
/// It is also used in the [`Gamepad`] component.
|
/// It is also used in the [`Gamepad`] component.
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||||
#[cfg_attr(
|
#[cfg_attr(
|
||||||
@ -593,7 +591,7 @@ pub enum GamepadButton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl GamepadButton {
|
impl GamepadButton {
|
||||||
/// Returns an array of all the standard [`GamepadButton`]
|
/// Returns an array of all the standard [`GamepadButton`].
|
||||||
pub const fn all() -> [GamepadButton; 19] {
|
pub const fn all() -> [GamepadButton; 19] {
|
||||||
[
|
[
|
||||||
GamepadButton::South,
|
GamepadButton::South,
|
||||||
@ -619,7 +617,7 @@ impl GamepadButton {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents gamepad input types that are mapped in the range [-1.0, 1.0]
|
/// Represents gamepad input types that are mapped in the range [-1.0, 1.0].
|
||||||
///
|
///
|
||||||
/// ## Usage
|
/// ## Usage
|
||||||
///
|
///
|
||||||
@ -665,14 +663,14 @@ impl GamepadAxis {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Encapsulation over [`GamepadAxis`] and [`GamepadButton`]
|
/// Encapsulation over [`GamepadAxis`] and [`GamepadButton`].
|
||||||
// This is done so Gamepad can share a single Axis<T> and simplifies the API by having only one get/get_unclamped method
|
// This is done so Gamepad can share a single Axis<T> and simplifies the API by having only one get/get_unclamped method
|
||||||
#[derive(Debug, Copy, Clone, Eq, Hash, PartialEq, From)]
|
#[derive(Debug, Copy, Clone, Eq, Hash, PartialEq, From)]
|
||||||
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, PartialEq))]
|
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, PartialEq))]
|
||||||
pub enum GamepadInput {
|
pub enum GamepadInput {
|
||||||
/// A [`GamepadAxis`]
|
/// A [`GamepadAxis`].
|
||||||
Axis(GamepadAxis),
|
Axis(GamepadAxis),
|
||||||
/// A [`GamepadButton`]
|
/// A [`GamepadButton`].
|
||||||
Button(GamepadButton),
|
Button(GamepadButton),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -928,9 +926,9 @@ impl ButtonSettings {
|
|||||||
/// threshold for an axis.
|
/// threshold for an axis.
|
||||||
/// Values that are higher than `livezone_upperbound` will be rounded up to 1.0.
|
/// Values that are higher than `livezone_upperbound` will be rounded up to 1.0.
|
||||||
/// Values that are lower than `livezone_lowerbound` will be rounded down to -1.0.
|
/// Values that are lower than `livezone_lowerbound` will be rounded down to -1.0.
|
||||||
/// Values that are in-between `deadzone_lowerbound` and `deadzone_upperbound` will be rounded
|
/// Values that are in-between `deadzone_lowerbound` and `deadzone_upperbound` will be rounded to 0.0.
|
||||||
/// to 0.0.
|
/// Otherwise, values will be linearly rescaled to fit into the sensitivity range.
|
||||||
/// Otherwise, values will not be rounded.
|
/// For example, a value that is one fourth of the way from `deadzone_upperbound` to `livezone_upperbound` will be scaled to 0.25.
|
||||||
///
|
///
|
||||||
/// The valid range is `[-1.0, 1.0]`.
|
/// The valid range is `[-1.0, 1.0]`.
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
@ -1041,7 +1039,7 @@ impl AxisSettings {
|
|||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
///
|
///
|
||||||
/// If the value passed is less than the dead zone upper bound,
|
/// If the value passed is less than the deadzone upper bound,
|
||||||
/// returns `AxisSettingsError::DeadZoneUpperBoundGreaterThanLiveZoneUpperBound`.
|
/// returns `AxisSettingsError::DeadZoneUpperBoundGreaterThanLiveZoneUpperBound`.
|
||||||
/// If the value passed is not in range [0.0..=1.0], returns `AxisSettingsError::LiveZoneUpperBoundOutOfRange`.
|
/// If the value passed is not in range [0.0..=1.0], returns `AxisSettingsError::LiveZoneUpperBoundOutOfRange`.
|
||||||
pub fn try_set_livezone_upperbound(&mut self, value: f32) -> Result<(), AxisSettingsError> {
|
pub fn try_set_livezone_upperbound(&mut self, value: f32) -> Result<(), AxisSettingsError> {
|
||||||
@ -1117,7 +1115,7 @@ impl AxisSettings {
|
|||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
///
|
///
|
||||||
/// If the value passed is less than the dead zone lower bound,
|
/// If the value passed is less than the deadzone lower bound,
|
||||||
/// returns `AxisSettingsError::LiveZoneLowerBoundGreaterThanDeadZoneLowerBound`.
|
/// returns `AxisSettingsError::LiveZoneLowerBoundGreaterThanDeadZoneLowerBound`.
|
||||||
/// If the value passed is not in range [-1.0..=0.0], returns `AxisSettingsError::LiveZoneLowerBoundOutOfRange`.
|
/// If the value passed is not in range [-1.0..=0.0], returns `AxisSettingsError::LiveZoneLowerBoundOutOfRange`.
|
||||||
pub fn try_set_livezone_lowerbound(&mut self, value: f32) -> Result<(), AxisSettingsError> {
|
pub fn try_set_livezone_lowerbound(&mut self, value: f32) -> Result<(), AxisSettingsError> {
|
||||||
@ -1213,39 +1211,135 @@ impl AxisSettings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Clamps the `raw_value` according to the `AxisSettings`.
|
/// Clamps the `raw_value` according to the `AxisSettings`.
|
||||||
pub fn clamp(&self, new_value: f32) -> f32 {
|
pub fn clamp(&self, raw_value: f32) -> f32 {
|
||||||
if self.deadzone_lowerbound <= new_value && new_value <= self.deadzone_upperbound {
|
if self.deadzone_lowerbound <= raw_value && raw_value <= self.deadzone_upperbound {
|
||||||
0.0
|
0.0
|
||||||
} else if new_value >= self.livezone_upperbound {
|
} else if raw_value >= self.livezone_upperbound {
|
||||||
1.0
|
1.0
|
||||||
} else if new_value <= self.livezone_lowerbound {
|
} else if raw_value <= self.livezone_lowerbound {
|
||||||
-1.0
|
-1.0
|
||||||
} else {
|
} else {
|
||||||
new_value
|
raw_value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Determines whether the change from `old_value` to `new_value` should
|
/// Determines whether the change from `old_raw_value` to `new_raw_value` should
|
||||||
/// be registered as a change, according to the [`AxisSettings`].
|
/// be registered as a change, according to the [`AxisSettings`].
|
||||||
fn should_register_change(&self, new_value: f32, old_value: Option<f32>) -> bool {
|
fn should_register_change(&self, new_raw_value: f32, old_raw_value: Option<f32>) -> bool {
|
||||||
if old_value.is_none() {
|
match old_raw_value {
|
||||||
return true;
|
None => true,
|
||||||
|
Some(old_raw_value) => ops::abs(new_raw_value - old_raw_value) >= self.threshold,
|
||||||
}
|
}
|
||||||
|
|
||||||
ops::abs(new_value - old_value.unwrap()) > self.threshold
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Filters the `new_value` based on the `old_value`, according to the [`AxisSettings`].
|
/// Filters the `new_raw_value` based on the `old_raw_value`, according to the [`AxisSettings`].
|
||||||
///
|
///
|
||||||
/// Returns the clamped `new_value` if the change exceeds the settings threshold,
|
/// Returns the clamped and scaled `new_raw_value` if the change exceeds the settings threshold,
|
||||||
/// and `None` otherwise.
|
/// and `None` otherwise.
|
||||||
pub fn filter(&self, new_value: f32, old_value: Option<f32>) -> Option<f32> {
|
fn filter(
|
||||||
let new_value = self.clamp(new_value);
|
&self,
|
||||||
|
new_raw_value: f32,
|
||||||
if self.should_register_change(new_value, old_value) {
|
old_raw_value: Option<f32>,
|
||||||
return Some(new_value);
|
) -> Option<FilteredAxisPosition> {
|
||||||
|
let clamped_unscaled = self.clamp(new_raw_value);
|
||||||
|
match self.should_register_change(clamped_unscaled, old_raw_value) {
|
||||||
|
true => Some(FilteredAxisPosition {
|
||||||
|
scaled: self.get_axis_position_from_value(clamped_unscaled),
|
||||||
|
raw: new_raw_value,
|
||||||
|
}),
|
||||||
|
false => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn get_axis_position_from_value(&self, value: f32) -> ScaledAxisWithDeadZonePosition {
|
||||||
|
if value < self.deadzone_upperbound && value > self.deadzone_lowerbound {
|
||||||
|
ScaledAxisWithDeadZonePosition::Dead
|
||||||
|
} else if value > self.livezone_upperbound {
|
||||||
|
ScaledAxisWithDeadZonePosition::AboveHigh
|
||||||
|
} else if value < self.livezone_lowerbound {
|
||||||
|
ScaledAxisWithDeadZonePosition::BelowLow
|
||||||
|
} else if value >= self.deadzone_upperbound {
|
||||||
|
ScaledAxisWithDeadZonePosition::High(linear_remapping(
|
||||||
|
value,
|
||||||
|
self.deadzone_upperbound..=self.livezone_upperbound,
|
||||||
|
0.0..=1.0,
|
||||||
|
))
|
||||||
|
} else if value <= self.deadzone_lowerbound {
|
||||||
|
ScaledAxisWithDeadZonePosition::Low(linear_remapping(
|
||||||
|
value,
|
||||||
|
self.livezone_lowerbound..=self.deadzone_lowerbound,
|
||||||
|
-1.0..=0.0,
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
unreachable!();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A linear remapping of `value` from `old` to `new`.
|
||||||
|
fn linear_remapping(value: f32, old: RangeInclusive<f32>, new: RangeInclusive<f32>) -> f32 {
|
||||||
|
// https://stackoverflow.com/a/929104
|
||||||
|
((value - old.start()) / (old.end() - old.start())) * (new.end() - new.start()) + new.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
/// Deadzone-aware axis position.
|
||||||
|
enum ScaledAxisWithDeadZonePosition {
|
||||||
|
/// The input clipped below the valid range of the axis.
|
||||||
|
BelowLow,
|
||||||
|
/// The input is lower than the deadzone.
|
||||||
|
Low(f32),
|
||||||
|
/// The input falls within the deadzone, meaning it is counted as 0.
|
||||||
|
Dead,
|
||||||
|
/// The input is higher than the deadzone.
|
||||||
|
High(f32),
|
||||||
|
/// The input clipped above the valid range of the axis.
|
||||||
|
AboveHigh,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct FilteredAxisPosition {
|
||||||
|
scaled: ScaledAxisWithDeadZonePosition,
|
||||||
|
raw: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ScaledAxisWithDeadZonePosition {
|
||||||
|
/// Converts the value into a float in the range [-1, 1].
|
||||||
|
fn to_f32(self) -> f32 {
|
||||||
|
match self {
|
||||||
|
ScaledAxisWithDeadZonePosition::BelowLow => -1.,
|
||||||
|
ScaledAxisWithDeadZonePosition::Low(scaled)
|
||||||
|
| ScaledAxisWithDeadZonePosition::High(scaled) => scaled,
|
||||||
|
ScaledAxisWithDeadZonePosition::Dead => 0.,
|
||||||
|
ScaledAxisWithDeadZonePosition::AboveHigh => 1.,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
/// Low/High-aware axis position.
|
||||||
|
enum ScaledAxisPosition {
|
||||||
|
/// The input fell short of the "low" value.
|
||||||
|
ClampedLow,
|
||||||
|
/// The input was in the normal range.
|
||||||
|
Scaled(f32),
|
||||||
|
/// The input surpassed the "high" value.
|
||||||
|
ClampedHigh,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct FilteredButtonAxisPosition {
|
||||||
|
scaled: ScaledAxisPosition,
|
||||||
|
raw: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ScaledAxisPosition {
|
||||||
|
/// Converts the value into a float in the range [0, 1].
|
||||||
|
fn to_f32(self) -> f32 {
|
||||||
|
match self {
|
||||||
|
ScaledAxisPosition::ClampedLow => 0.,
|
||||||
|
ScaledAxisPosition::Scaled(scaled) => scaled,
|
||||||
|
ScaledAxisPosition::ClampedHigh => 1.,
|
||||||
}
|
}
|
||||||
None
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1300,27 +1394,48 @@ impl ButtonAxisSettings {
|
|||||||
raw_value
|
raw_value
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Determines whether the change from an `old_value` to a `new_value` should
|
/// Determines whether the change from an `old_raw_value` to a `new_raw_value` should
|
||||||
/// be registered as a change event, according to the specified settings.
|
/// be registered as a change event, according to the specified settings.
|
||||||
fn should_register_change(&self, new_value: f32, old_value: Option<f32>) -> bool {
|
fn should_register_change(&self, new_raw_value: f32, old_raw_value: Option<f32>) -> bool {
|
||||||
if old_value.is_none() {
|
match old_raw_value {
|
||||||
return true;
|
None => true,
|
||||||
|
Some(old_raw_value) => ops::abs(new_raw_value - old_raw_value) >= self.threshold,
|
||||||
}
|
}
|
||||||
|
|
||||||
ops::abs(new_value - old_value.unwrap()) > self.threshold
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Filters the `new_value` based on the `old_value`, according to the [`ButtonAxisSettings`].
|
/// Filters the `new_raw_value` based on the `old_raw_value`, according to the [`ButtonAxisSettings`].
|
||||||
///
|
///
|
||||||
/// Returns the clamped `new_value`, according to the [`ButtonAxisSettings`], if the change
|
/// Returns the clamped and scaled `new_raw_value`, according to the [`ButtonAxisSettings`], if the change
|
||||||
/// exceeds the settings threshold, and `None` otherwise.
|
/// exceeds the settings threshold, and `None` otherwise.
|
||||||
pub fn filter(&self, new_value: f32, old_value: Option<f32>) -> Option<f32> {
|
fn filter(
|
||||||
let new_value = self.clamp(new_value);
|
&self,
|
||||||
|
new_raw_value: f32,
|
||||||
if self.should_register_change(new_value, old_value) {
|
old_raw_value: Option<f32>,
|
||||||
return Some(new_value);
|
) -> Option<FilteredButtonAxisPosition> {
|
||||||
|
let clamped_unscaled = self.clamp(new_raw_value);
|
||||||
|
match self.should_register_change(clamped_unscaled, old_raw_value) {
|
||||||
|
true => Some(FilteredButtonAxisPosition {
|
||||||
|
scaled: self.get_axis_position_from_value(clamped_unscaled),
|
||||||
|
raw: new_raw_value,
|
||||||
|
}),
|
||||||
|
false => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clamps and scales the `value` according to the specified settings.
|
||||||
|
///
|
||||||
|
/// If the `value` is:
|
||||||
|
/// - lower than or equal to `low` it will be rounded to 0.0.
|
||||||
|
/// - higher than or equal to `high` it will be rounded to 1.0.
|
||||||
|
/// - Otherwise, it will be scaled from (low, high) to (0, 1).
|
||||||
|
fn get_axis_position_from_value(&self, value: f32) -> ScaledAxisPosition {
|
||||||
|
if value <= self.low {
|
||||||
|
ScaledAxisPosition::ClampedLow
|
||||||
|
} else if value >= self.high {
|
||||||
|
ScaledAxisPosition::ClampedHigh
|
||||||
|
} else {
|
||||||
|
ScaledAxisPosition::Scaled(linear_remapping(value, self.low..=self.high, 0.0..=1.0))
|
||||||
}
|
}
|
||||||
None
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1328,7 +1443,7 @@ impl ButtonAxisSettings {
|
|||||||
///
|
///
|
||||||
/// On connection, adds the components representing a [`Gamepad`] to the entity.
|
/// On connection, adds the components representing a [`Gamepad`] to the entity.
|
||||||
/// On disconnection, removes the [`Gamepad`] and other related components.
|
/// On disconnection, removes the [`Gamepad`] and other related components.
|
||||||
/// Entities are left alive and might leave components like [`GamepadSettings`] to preserve state in the case of a reconnection
|
/// Entities are left alive and might leave components like [`GamepadSettings`] to preserve state in the case of a reconnection.
|
||||||
///
|
///
|
||||||
/// ## Note
|
/// ## Note
|
||||||
///
|
///
|
||||||
@ -1441,9 +1556,9 @@ pub fn gamepad_event_processing_system(
|
|||||||
else {
|
else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
gamepad_axis.analog.set(axis, filtered_value.raw);
|
||||||
gamepad_axis.analog.set(axis, filtered_value);
|
let send_event =
|
||||||
let send_event = GamepadAxisChangedEvent::new(gamepad, axis, filtered_value);
|
GamepadAxisChangedEvent::new(gamepad, axis, filtered_value.scaled.to_f32());
|
||||||
processed_axis_events.send(send_event);
|
processed_axis_events.send(send_event);
|
||||||
processed_events.send(GamepadEvent::from(send_event));
|
processed_events.send(GamepadEvent::from(send_event));
|
||||||
}
|
}
|
||||||
@ -1463,9 +1578,9 @@ pub fn gamepad_event_processing_system(
|
|||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let button_settings = settings.get_button_settings(button);
|
let button_settings = settings.get_button_settings(button);
|
||||||
gamepad_buttons.analog.set(button, filtered_value);
|
gamepad_buttons.analog.set(button, filtered_value.raw);
|
||||||
|
|
||||||
if button_settings.is_released(filtered_value) {
|
if button_settings.is_released(filtered_value.raw) {
|
||||||
// Check if button was previously pressed
|
// Check if button was previously pressed
|
||||||
if gamepad_buttons.pressed(button) {
|
if gamepad_buttons.pressed(button) {
|
||||||
processed_digital_events.send(GamepadButtonStateChangedEvent::new(
|
processed_digital_events.send(GamepadButtonStateChangedEvent::new(
|
||||||
@ -1477,7 +1592,7 @@ pub fn gamepad_event_processing_system(
|
|||||||
// We don't have to check if the button was previously pressed here
|
// We don't have to check if the button was previously pressed here
|
||||||
// because that check is performed within Input<T>::release()
|
// because that check is performed within Input<T>::release()
|
||||||
gamepad_buttons.digital.release(button);
|
gamepad_buttons.digital.release(button);
|
||||||
} else if button_settings.is_pressed(filtered_value) {
|
} else if button_settings.is_pressed(filtered_value.raw) {
|
||||||
// Check if button was previously not pressed
|
// Check if button was previously not pressed
|
||||||
if !gamepad_buttons.pressed(button) {
|
if !gamepad_buttons.pressed(button) {
|
||||||
processed_digital_events.send(GamepadButtonStateChangedEvent::new(
|
processed_digital_events.send(GamepadButtonStateChangedEvent::new(
|
||||||
@ -1494,8 +1609,12 @@ pub fn gamepad_event_processing_system(
|
|||||||
} else {
|
} else {
|
||||||
ButtonState::Released
|
ButtonState::Released
|
||||||
};
|
};
|
||||||
let send_event =
|
let send_event = GamepadButtonChangedEvent::new(
|
||||||
GamepadButtonChangedEvent::new(gamepad, button, button_state, filtered_value);
|
gamepad,
|
||||||
|
button,
|
||||||
|
button_state,
|
||||||
|
filtered_value.scaled.to_f32(),
|
||||||
|
);
|
||||||
processed_analog_events.send(send_event);
|
processed_analog_events.send(send_event);
|
||||||
processed_events.send(GamepadEvent::from(send_event));
|
processed_events.send(GamepadEvent::from(send_event));
|
||||||
}
|
}
|
||||||
@ -1649,130 +1768,161 @@ mod tests {
|
|||||||
|
|
||||||
fn test_button_axis_settings_filter(
|
fn test_button_axis_settings_filter(
|
||||||
settings: ButtonAxisSettings,
|
settings: ButtonAxisSettings,
|
||||||
new_value: f32,
|
new_raw_value: f32,
|
||||||
old_value: Option<f32>,
|
old_raw_value: Option<f32>,
|
||||||
expected: Option<f32>,
|
expected: Option<f32>,
|
||||||
) {
|
) {
|
||||||
let actual = settings.filter(new_value, old_value);
|
let actual = settings
|
||||||
|
.filter(new_raw_value, old_raw_value)
|
||||||
|
.map(|f| f.scaled.to_f32());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
expected, actual,
|
expected, actual,
|
||||||
"Testing filtering for {settings:?} with new_value = {new_value:?}, old_value = {old_value:?}",
|
"Testing filtering for {settings:?} with new_raw_value = {new_raw_value:?}, old_raw_value = {old_raw_value:?}",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_button_axis_settings_default_filter() {
|
fn test_button_axis_settings_default_filter() {
|
||||||
let cases = [
|
let cases = [
|
||||||
|
// clamped
|
||||||
(1.0, None, Some(1.0)),
|
(1.0, None, Some(1.0)),
|
||||||
(0.99, None, Some(1.0)),
|
(0.99, None, Some(1.0)),
|
||||||
(0.96, None, Some(1.0)),
|
(0.96, None, Some(1.0)),
|
||||||
(0.95, None, Some(1.0)),
|
(0.95, None, Some(1.0)),
|
||||||
(0.9499, None, Some(0.9499)),
|
// linearly rescaled from 0.05..=0.95 to 0.0..=1.0
|
||||||
(0.84, None, Some(0.84)),
|
(0.9499, None, Some(0.9998889)),
|
||||||
(0.43, None, Some(0.43)),
|
(0.84, None, Some(0.87777776)),
|
||||||
(0.05001, None, Some(0.05001)),
|
(0.43, None, Some(0.42222223)),
|
||||||
|
(0.05001, None, Some(0.000011109644)),
|
||||||
|
// clamped
|
||||||
(0.05, None, Some(0.0)),
|
(0.05, None, Some(0.0)),
|
||||||
(0.04, None, Some(0.0)),
|
(0.04, None, Some(0.0)),
|
||||||
(0.01, None, Some(0.0)),
|
(0.01, None, Some(0.0)),
|
||||||
(0.0, None, Some(0.0)),
|
(0.0, None, Some(0.0)),
|
||||||
];
|
];
|
||||||
|
|
||||||
for (new_value, old_value, expected) in cases {
|
for (new_raw_value, old_raw_value, expected) in cases {
|
||||||
let settings = ButtonAxisSettings::default();
|
let settings = ButtonAxisSettings::default();
|
||||||
test_button_axis_settings_filter(settings, new_value, old_value, expected);
|
test_button_axis_settings_filter(settings, new_raw_value, old_raw_value, expected);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_button_axis_settings_default_filter_with_old_value() {
|
fn test_button_axis_settings_default_filter_with_old_raw_value() {
|
||||||
let cases = [
|
let cases = [
|
||||||
(0.43, Some(0.44001), Some(0.43)),
|
// 0.43 gets rescaled to 0.42222223 (0.05..=0.95 -> 0.0..=1.0)
|
||||||
|
(0.43, Some(0.44001), Some(0.42222223)),
|
||||||
(0.43, Some(0.44), None),
|
(0.43, Some(0.44), None),
|
||||||
(0.43, Some(0.43), None),
|
(0.43, Some(0.43), None),
|
||||||
(0.43, Some(0.41999), Some(0.43)),
|
(0.43, Some(0.41999), Some(0.42222223)),
|
||||||
(0.43, Some(0.17), Some(0.43)),
|
(0.43, Some(0.17), Some(0.42222223)),
|
||||||
(0.43, Some(0.84), Some(0.43)),
|
(0.43, Some(0.84), Some(0.42222223)),
|
||||||
(0.05, Some(0.055), Some(0.0)),
|
(0.05, Some(0.055), Some(0.0)),
|
||||||
(0.95, Some(0.945), Some(1.0)),
|
(0.95, Some(0.945), Some(1.0)),
|
||||||
];
|
];
|
||||||
|
|
||||||
for (new_value, old_value, expected) in cases {
|
for (new_raw_value, old_raw_value, expected) in cases {
|
||||||
let settings = ButtonAxisSettings::default();
|
let settings = ButtonAxisSettings::default();
|
||||||
test_button_axis_settings_filter(settings, new_value, old_value, expected);
|
test_button_axis_settings_filter(settings, new_raw_value, old_raw_value, expected);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test_axis_settings_filter(
|
fn test_axis_settings_filter(
|
||||||
settings: AxisSettings,
|
settings: AxisSettings,
|
||||||
new_value: f32,
|
new_raw_value: f32,
|
||||||
old_value: Option<f32>,
|
old_raw_value: Option<f32>,
|
||||||
expected: Option<f32>,
|
expected: Option<f32>,
|
||||||
) {
|
) {
|
||||||
let actual = settings.filter(new_value, old_value);
|
let actual = settings.filter(new_raw_value, old_raw_value);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
expected, actual,
|
expected, actual.map(|f| f.scaled.to_f32()),
|
||||||
"Testing filtering for {settings:?} with new_value = {new_value:?}, old_value = {old_value:?}",
|
"Testing filtering for {settings:?} with new_raw_value = {new_raw_value:?}, old_raw_value = {old_raw_value:?}",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_axis_settings_default_filter() {
|
fn test_axis_settings_default_filter() {
|
||||||
|
// new (raw), expected (rescaled linearly)
|
||||||
let cases = [
|
let cases = [
|
||||||
|
// high enough to round to 1.0
|
||||||
(1.0, Some(1.0)),
|
(1.0, Some(1.0)),
|
||||||
(0.99, Some(1.0)),
|
(0.99, Some(1.0)),
|
||||||
(0.96, Some(1.0)),
|
(0.96, Some(1.0)),
|
||||||
(0.95, Some(1.0)),
|
(0.95, Some(1.0)),
|
||||||
(0.9499, Some(0.9499)),
|
// for the following, remember that 0.05 is the "low" value and 0.95 is the "high" value
|
||||||
(0.84, Some(0.84)),
|
// barely below the high value means barely below 1 after scaling
|
||||||
(0.43, Some(0.43)),
|
(0.9499, Some(0.9998889)), // scaled as: (0.9499 - 0.05) / (0.95 - 0.05)
|
||||||
(0.05001, Some(0.05001)),
|
(0.84, Some(0.87777776)), // scaled as: (0.84 - 0.05) / (0.95 - 0.05)
|
||||||
|
(0.43, Some(0.42222223)), // scaled as: (0.43 - 0.05) / (0.95 - 0.05)
|
||||||
|
// barely above the low value means barely above 0 after scaling
|
||||||
|
(0.05001, Some(0.000011109644)), // scaled as: (0.05001 - 0.05) / (0.95 - 0.05)
|
||||||
|
// low enough to be rounded to 0 (dead zone)
|
||||||
(0.05, Some(0.0)),
|
(0.05, Some(0.0)),
|
||||||
(0.04, Some(0.0)),
|
(0.04, Some(0.0)),
|
||||||
(0.01, Some(0.0)),
|
(0.01, Some(0.0)),
|
||||||
(0.0, Some(0.0)),
|
(0.0, Some(0.0)),
|
||||||
|
// same exact tests as above, but below 0 (bottom half of the dead zone and live zone)
|
||||||
|
// low enough to be rounded to -1
|
||||||
(-1.0, Some(-1.0)),
|
(-1.0, Some(-1.0)),
|
||||||
(-0.99, Some(-1.0)),
|
(-0.99, Some(-1.0)),
|
||||||
(-0.96, Some(-1.0)),
|
(-0.96, Some(-1.0)),
|
||||||
(-0.95, Some(-1.0)),
|
(-0.95, Some(-1.0)),
|
||||||
(-0.9499, Some(-0.9499)),
|
// scaled inputs
|
||||||
(-0.84, Some(-0.84)),
|
(-0.9499, Some(-0.9998889)), // scaled as: (-0.9499 - -0.05) / (-0.95 - -0.05)
|
||||||
(-0.43, Some(-0.43)),
|
(-0.84, Some(-0.87777776)), // scaled as: (-0.84 - -0.05) / (-0.95 - -0.05)
|
||||||
(-0.05001, Some(-0.05001)),
|
(-0.43, Some(-0.42222226)), // scaled as: (-0.43 - -0.05) / (-0.95 - -0.05)
|
||||||
|
(-0.05001, Some(-0.000011146069)), // scaled as: (-0.05001 - -0.05) / (-0.95 - -0.05)
|
||||||
|
// high enough to be rounded to 0 (dead zone)
|
||||||
(-0.05, Some(0.0)),
|
(-0.05, Some(0.0)),
|
||||||
(-0.04, Some(0.0)),
|
(-0.04, Some(0.0)),
|
||||||
(-0.01, Some(0.0)),
|
(-0.01, Some(0.0)),
|
||||||
];
|
];
|
||||||
|
|
||||||
for (new_value, expected) in cases {
|
for (new_raw_value, expected) in cases {
|
||||||
let settings = AxisSettings::new(-0.95, -0.05, 0.05, 0.95, 0.01).unwrap();
|
let settings = AxisSettings::new(-0.95, -0.05, 0.05, 0.95, 0.01).unwrap();
|
||||||
test_axis_settings_filter(settings, new_value, None, expected);
|
test_axis_settings_filter(settings, new_raw_value, None, expected);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_axis_settings_default_filter_with_old_values() {
|
fn test_axis_settings_default_filter_with_old_raw_values() {
|
||||||
|
let threshold = 0.01;
|
||||||
|
// expected values are hardcoded to be rescaled to from 0.05..=0.95 to 0.0..=1.0
|
||||||
|
// new (raw), old (raw), expected
|
||||||
let cases = [
|
let cases = [
|
||||||
(0.43, Some(0.44001), Some(0.43)),
|
// enough increase to change
|
||||||
(0.43, Some(0.44), None),
|
(0.43, Some(0.43 + threshold * 1.1), Some(0.42222223)),
|
||||||
(0.43, Some(0.43), None),
|
// enough decrease to change
|
||||||
(0.43, Some(0.41999), Some(0.43)),
|
(0.43, Some(0.43 - threshold * 1.1), Some(0.42222223)),
|
||||||
(0.43, Some(0.17), Some(0.43)),
|
// not enough increase to change
|
||||||
(0.43, Some(0.84), Some(0.43)),
|
(0.43, Some(0.43 + threshold * 0.9), None),
|
||||||
(0.05, Some(0.055), Some(0.0)),
|
// not enough decrease to change
|
||||||
(0.95, Some(0.945), Some(1.0)),
|
(0.43, Some(0.43 - threshold * 0.9), None),
|
||||||
(-0.43, Some(-0.44001), Some(-0.43)),
|
// enough increase to change
|
||||||
(-0.43, Some(-0.44), None),
|
(-0.43, Some(-0.43 + threshold * 1.1), Some(-0.42222226)),
|
||||||
(-0.43, Some(-0.43), None),
|
// enough decrease to change
|
||||||
(-0.43, Some(-0.41999), Some(-0.43)),
|
(-0.43, Some(-0.43 - threshold * 1.1), Some(-0.42222226)),
|
||||||
(-0.43, Some(-0.17), Some(-0.43)),
|
// not enough increase to change
|
||||||
(-0.43, Some(-0.84), Some(-0.43)),
|
(-0.43, Some(-0.43 + threshold * 0.9), None),
|
||||||
(-0.05, Some(-0.055), Some(0.0)),
|
// not enough decrease to change
|
||||||
(-0.95, Some(-0.945), Some(-1.0)),
|
(-0.43, Some(-0.43 - threshold * 0.9), None),
|
||||||
|
// test upper deadzone logic
|
||||||
|
(0.05, Some(0.0), None),
|
||||||
|
(0.06, Some(0.0), Some(0.0111111095)),
|
||||||
|
// test lower deadzone logic
|
||||||
|
(-0.05, Some(0.0), None),
|
||||||
|
(-0.06, Some(0.0), Some(-0.011111081)),
|
||||||
|
// test upper livezone logic
|
||||||
|
(0.95, Some(1.0), None),
|
||||||
|
(0.94, Some(1.0), Some(0.9888889)),
|
||||||
|
// test lower livezone logic
|
||||||
|
(-0.95, Some(-1.0), None),
|
||||||
|
(-0.94, Some(-1.0), Some(-0.9888889)),
|
||||||
];
|
];
|
||||||
|
|
||||||
for (new_value, old_value, expected) in cases {
|
for (new_raw_value, old_raw_value, expected) in cases {
|
||||||
let settings = AxisSettings::new(-0.95, -0.05, 0.05, 0.95, 0.01).unwrap();
|
let settings = AxisSettings::new(-0.95, -0.05, 0.05, 0.95, threshold).unwrap();
|
||||||
test_axis_settings_filter(settings, new_value, old_value, expected);
|
test_axis_settings_filter(settings, new_raw_value, old_raw_value, expected);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user