From 36441c71a8f73e978b05f27b33e735ec14a07bf4 Mon Sep 17 00:00:00 2001 From: Shute Date: Wed, 18 Sep 2024 10:27:00 +0800 Subject: [PATCH] Add new builders for input processors and extensions for those implementing `WithInputProcessorExt` traits (#625) * new methods * new processor methods * minor * minor --- RELEASES.md | 10 + src/input_processing/dual_axis/circle.rs | 8 +- src/input_processing/dual_axis/mod.rs | 416 ++++++++++++++++++++-- src/input_processing/dual_axis/range.rs | 282 ++++++++++++++- src/input_processing/single_axis/mod.rs | 110 +++++- src/input_processing/single_axis/range.rs | 66 +++- 6 files changed, 840 insertions(+), 52 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index 1579882b..6a4e7506 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -25,6 +25,16 @@ - Reflect `Component` and `Resource`, which enables accessing the data in the type registry +#### Input Processors + +- allowed creating `DualAxisBounds`, `DualAxisExclusion`, and `DualAxisDeadZone` from their struct definitions directly. +- added `at_least` and `at_most` methods for those implementing `WithAxisProcessorExt` trait. +- added `at_least`, `at_least_only_x`, `at_least_only_y`, `at_most`, `at_most_only_x`, and `at_most_only_y` methods for those implementing `WithDualAxisProcessorExt` trait. +- added `only_positive` and `only_negative` builders for `AxisDeadZone` and `AxisExclusion`. + - added corresponding extension methods for those implementing `WithAxisProcessorExt` trait. +- added `only_positive`, `only_positive_x`, `only_positive_y`, `only_negative`, `only_negative_x`, and `only_negative_y` builders for `DualAxisDeadZone` and `DualAxisExclusion`. + - added corresponding extension methods for those implementing `WithDualAxisProcessorExt` trait. + #### ActionDiffEvent - Implement `MapEntities`, which lets networking crates translate owner entity IDs between ECS worlds diff --git a/src/input_processing/dual_axis/circle.rs b/src/input_processing/dual_axis/circle.rs index 01d28f79..b1f059a8 100644 --- a/src/input_processing/dual_axis/circle.rs +++ b/src/input_processing/dual_axis/circle.rs @@ -48,7 +48,7 @@ impl CircleBounds { /// /// # Requirements /// - /// - `threshold` >= `0.0`. + /// - `max` >= `0.0`. /// /// # Panics /// @@ -56,9 +56,9 @@ impl CircleBounds { #[doc(alias = "magnitude")] #[doc(alias = "from_radius")] #[inline] - pub fn new(threshold: f32) -> Self { - assert!(threshold >= 0.0); - Self { radius: threshold } + pub fn new(max: f32) -> Self { + assert!(max >= 0.0); + Self { radius: max } } /// Returns the radius of the bounds. diff --git a/src/input_processing/dual_axis/mod.rs b/src/input_processing/dual_axis/mod.rs index 898592df..463572f9 100644 --- a/src/input_processing/dual_axis/mod.rs +++ b/src/input_processing/dual_axis/mod.rs @@ -205,21 +205,79 @@ pub trait WithDualAxisProcessingPipelineExt: Sized { self.with_processor(DualAxisBounds::symmetric_all(threshold)) } + /// Appends a [`DualAxisBounds`] processor as the next processing step, + /// restricting values to a minimum value on both axes. + #[inline] + fn at_least(self, min: f32) -> Self { + self.with_processor(DualAxisBounds::at_least_all(min)) + } + + /// Appends a [`DualAxisBounds`] processor as the next processing step, + /// restricting X values to a minimum value. + #[inline] + fn at_least_only_x(self, min: f32) -> Self { + self.with_processor(DualAxisBounds::at_least_only_x(min)) + } + + /// Appends a [`DualAxisBounds`] processor as the next processing step, + /// restricting Y values to a minimum value. + #[inline] + fn at_least_only_y(self, min: f32) -> Self { + self.with_processor(DualAxisBounds::at_least_only_y(min)) + } + + /// Appends a [`DualAxisBounds`] processor as the next processing step, + /// restricting values to a maximum value on both axes. + #[inline] + fn at_most(self, min: f32) -> Self { + self.with_processor(DualAxisBounds::at_most_all(min)) + } + + /// Appends a [`DualAxisBounds`] processor as the next processing step, + /// restricting X values to a maximum value. + #[inline] + fn at_most_only_x(self, min: f32) -> Self { + self.with_processor(DualAxisBounds::at_most_only_x(min)) + } + + /// Appends a [`DualAxisBounds`] processor as the next processing step, + /// restricting Y values to a maximum value. + #[inline] + fn at_most_only_y(self, min: f32) -> Self { + self.with_processor(DualAxisBounds::at_most_only_y(min)) + } + /// Appends a [`CircleBounds`] processor as the next processing step, /// restricting values to a `max` magnitude. + /// + /// # Requirements + /// + /// - `max` >= `0.0`. + /// + /// # Panics + /// + /// Panics if the requirements aren't met. #[inline] fn with_circle_bounds(self, max: f32) -> Self { self.with_processor(CircleBounds::new(max)) } /// Appends a [`DualAxisDeadZone`] processor as the next processing step, - /// excluding values within the dead zone range `[min, max]` on both axes, + /// excluding values within the dead zone range `[negative_max, positive_min]` on both axes, /// treating them as zeros, then normalizing non-excluded input values into the "live zone", /// the remaining range within the [`DualAxisBounds::symmetric_all(1.0)`](DualAxisBounds::default) /// after dead zone exclusion. + /// + /// # Requirements + /// + /// - `negative_max` <= `0.0` <= `positive_min`. + /// + /// # Panics + /// + /// Panics if the requirements aren't met. #[inline] - fn with_deadzone(self, min: f32, max: f32) -> Self { - self.with_processor(DualAxisDeadZone::all(min, max)) + fn with_deadzone(self, negative_max: f32, positive_min: f32) -> Self { + self.with_processor(DualAxisDeadZone::all(negative_max, positive_min)) } /// Appends a [`DualAxisDeadZone`] processor as the next processing step, @@ -227,111 +285,407 @@ pub trait WithDualAxisProcessingPipelineExt: Sized { /// treating them as zeros, then normalizing non-excluded input values into the "live zone", /// the remaining range within the [`DualAxisBounds::symmetric_all(1.0)`](DualAxisBounds::default) /// after dead zone exclusion. + /// + /// # Requirements + /// + /// - `threshold` >= `0.0`. + /// + /// # Panics + /// + /// Panics if the requirements aren't met. #[inline] fn with_deadzone_symmetric(self, threshold: f32) -> Self { self.with_processor(DualAxisDeadZone::symmetric_all(threshold)) } /// Appends a [`DualAxisDeadZone`] processor as the next processing step, - /// excluding values within the dead zone range `[min, max]` on the X-axis, - /// treating them as zeros, then normalizing non-excluded input values into the "live zone", - /// the remaining range within the [`DualAxisBounds::symmetric_all(1.0)`](DualAxisBounds::default) + /// only passing positive values that greater than `positive_min` on both axes + /// and then normalizing them into the "live zone" range `[positive_min, 1.0]`. + /// + /// # Requirements + /// + /// - `positive_min` >= `0.0`. + /// + /// # Panics + /// + /// Panics if the requirements aren't met. + #[inline] + fn only_positive(self, positive_min: f32) -> Self { + self.with_processor(DualAxisDeadZone::only_positive_all(positive_min)) + } + + /// Appends a [`DualAxisDeadZone`] processor as the next processing step, + /// only passing negative values that less than `negative_max` on both axes + /// and then normalizing them into the "live zone" range `[-1.0, negative_max]`. + /// + /// # Requirements + /// + /// - `negative_max` <= `0.0`. + /// + /// # Panics + /// + /// Panics if the requirements aren't met. + #[inline] + fn only_negative(self, negative_max: f32) -> Self { + self.with_processor(DualAxisDeadZone::only_negative_all(negative_max)) + } + + /// Appends a [`DualAxisDeadZone`] processor as the next processing step, + /// excluding values within the range `[negative_max, positive_min]` on the X-axis, + /// treating them as zeros, then normalizing non-excluded X values into the "live zone", + /// the remaining range within the [`AxisBounds::symmetric(1.0)`](super::AxisBounds::default) /// after dead zone exclusion. + /// + /// # Requirements + /// + /// - `negative_max` <= `0.0` <= `positive_min`. + /// + /// # Panics + /// + /// Panics if the requirements aren't met. #[inline] - fn with_deadzone_x(self, min: f32, max: f32) -> Self { - self.with_processor(DualAxisDeadZone::only_x(min, max)) + fn with_deadzone_x(self, negative_max: f32, positive_min: f32) -> Self { + self.with_processor(DualAxisDeadZone::only_x(negative_max, positive_min)) } /// Appends a [`DualAxisDeadZone`] processor as the next processing step, - /// excluding values within the dead zone range `[-threshold, threshold]` on the X-axis, - /// treating them as zeros, then normalizing non-excluded input values into the "live zone", - /// the remaining range within the [`DualAxisBounds::symmetric_all(1.0)`](DualAxisBounds::default) + /// excluding values within the range `[-threshold, threshold]` on the X-axis, + /// treating them as zeros, then normalizing non-excluded X values into the "live zone", + /// the remaining range within the [`AxisBounds::symmetric(1.0)`](super::AxisBounds::default) /// after dead zone exclusion. + /// + /// # Requirements + /// + /// - `threshold` >= `0.0`. + /// + /// # Panics + /// + /// Panics if the requirements aren't met. #[inline] fn with_deadzone_x_symmetric(self, threshold: f32) -> Self { self.with_processor(DualAxisDeadZone::symmetric_only_x(threshold)) } /// Appends a [`DualAxisDeadZone`] processor as the next processing step, - /// excluding values within the dead zone range `[min, max]` on the Y-axis, - /// treating them as zeros, then normalizing non-excluded input values into the "live zone", - /// the remaining range within the [`DualAxisBounds::symmetric_all(1.0)`](DualAxisBounds::default) + /// only excluding X values that less than or equal to `positive_min`, treating them as zeros + /// and then normalizing non-excluded X values into the "live zone" range `[positive_min, 1.0]`. + /// + /// # Requirements + /// + /// - `positive_min` >= `0.0`. + /// + /// # Panics + /// + /// Panics if the requirements aren't met. + #[inline] + fn only_positive_x(self, positive_min: f32) -> Self { + self.with_processor(DualAxisDeadZone::only_positive_x(positive_min)) + } + + /// Appends a [`DualAxisDeadZone`] processor as the next processing step, + /// only excluding X values that greater than or equal to `negative_max`, treating them as zeros + /// and then normalizing non-excluded X values into the "live zone" range `[-1.0, negative_max]`. + /// + /// # Requirements + /// + /// - `negative_max` <= `0.0`. + /// + /// # Panics + /// + /// Panics if the requirements aren't met. + #[inline] + fn only_negative_x(self, negative_max: f32) -> Self { + self.with_processor(DualAxisDeadZone::only_negative_x(negative_max)) + } + + /// Appends a [`DualAxisDeadZone`] processor as the next processing step, + /// excluding values within the range `[negative_max, positive_min]` on the Y-axis, + /// treating them as zeros, then normalizing non-excluded Y values into the "live zone", + /// the remaining range within the [`AxisBounds::symmetric(1.0)`](super::AxisBounds::default) /// after dead zone exclusion. + /// + /// # Requirements + /// + /// - `negative_max` <= `0.0` <= `positive_min`. + /// + /// # Panics + /// + /// Panics if the requirements aren't met. #[inline] - fn with_deadzone_y(self, min: f32, max: f32) -> Self { - self.with_processor(DualAxisDeadZone::only_y(min, max)) + fn with_deadzone_y(self, negative_max: f32, positive_min: f32) -> Self { + self.with_processor(DualAxisDeadZone::only_y(negative_max, positive_min)) } /// Appends a [`DualAxisDeadZone`] processor as the next processing step, - /// excluding values within the deadzone range `[-threshold, threshold]` on the Y-axis, - /// treating them as zeros, then normalizing non-excluded input values into the "live zone", - /// the remaining range within the [`DualAxisBounds::symmetric_all(1.0)`](DualAxisBounds::default) + /// excluding values within the range `[-threshold, threshold]` on the Y-axis, + /// treating them as zeros, then normalizing non-excluded Y values into the "live zone", + /// the remaining range within the [`AxisBounds::symmetric(1.0)`](super::AxisBounds::default) /// after dead zone exclusion. + /// + /// # Requirements + /// + /// - `threshold` >= `0.0`. + /// + /// # Panics + /// + /// Panics if the requirements aren't met. #[inline] fn with_deadzone_y_symmetric(self, threshold: f32) -> Self { self.with_processor(DualAxisDeadZone::symmetric_only_y(threshold)) } + /// Appends a [`DualAxisDeadZone`] processor as the next processing step, + /// only excluding Y values that less than or equal to `positive_min`, treating them as zeros + /// and then normalizing non-excluded Y values into the range `[positive_min, 1.0]`. + /// + /// # Requirements + /// + /// - `positive_min` >= `0.0`. + /// + /// # Panics + /// + /// Panics if the requirements aren't met. + #[inline] + fn only_positive_y(self, positive_min: f32) -> Self { + self.with_processor(DualAxisDeadZone::only_positive_y(positive_min)) + } + + /// Appends a [`DualAxisDeadZone`] processor as the next processing step, + /// only excluding Y values that greater than or equal to `negative_max`, treating them as zeros + /// and then normalizing non-excluded Y values into the range `[-1.0, negative_max]`. + /// + /// # Requirements + /// + /// - `negative_max` <= `0.0`. + /// + /// # Panics + /// + /// Panics if the requirements aren't met. + #[inline] + fn only_negative_y(self, negative_max: f32) -> Self { + self.with_processor(DualAxisDeadZone::only_negative_y(negative_max)) + } + /// Appends a [`CircleDeadZone`] processor as the next processing step, /// ignoring values below a `min` magnitude, treating them as zeros, /// then normalizing non-excluded input values into the "live zone", /// the remaining range within the [`CircleBounds::new(1.0)`](CircleBounds::default) /// after dead zone exclusion. + /// + /// # Requirements + /// + /// - `min` >= `0.0`. + /// + /// # Panics + /// + /// Panics if the requirements aren't met. #[inline] fn with_circle_deadzone(self, min: f32) -> Self { self.with_processor(CircleDeadZone::new(min)) } /// Appends a [`DualAxisExclusion`] processor as the next processing step, - /// ignoring values within the dead zone range `[min, max]` on both axes, + /// ignoring values within the range `[negative_max, positive_min]` on both axes, /// treating them as zeros. + /// + /// # Requirements + /// + /// - `negative_max` <= `0.0` <= `positive_min`. + /// + /// # Panics + /// + /// Panics if the requirements aren't met. #[inline] - fn with_deadzone_unscaled(self, min: f32, max: f32) -> Self { - self.with_processor(DualAxisExclusion::all(min, max)) + fn with_deadzone_unscaled(self, negative_max: f32, positive_min: f32) -> Self { + self.with_processor(DualAxisExclusion::all(negative_max, positive_min)) } /// Appends a [`DualAxisExclusion`] processor as the next processing step, - /// ignoring values within the dead zone range `[-threshold, threshold]` on both axes, + /// ignoring values within the range `[-threshold, threshold]` on both axes, /// treating them as zeros. + /// + /// # Requirements + /// + /// - `threshold` >= `0.0`. + /// + /// # Panics + /// + /// Panics if the requirements aren't met. #[inline] fn with_deadzone_symmetric_unscaled(self, threshold: f32) -> Self { self.with_processor(DualAxisExclusion::symmetric_all(threshold)) } /// Appends a [`DualAxisExclusion`] processor as the next processing step, - /// only ignoring values within the dead zone range `[min, max]` on the X-axis, + /// only passing positive values that greater than `positive_min` on both axes, /// treating them as zeros. + /// + /// # Requirements + /// + /// - `positive_min` >= `0.0`. + /// + /// # Panics + /// + /// Panics if the requirements aren't met. + #[inline] + fn only_positive_unscaled(self, positive_min: f32) -> Self { + self.with_processor(DualAxisExclusion::only_positive_all(positive_min)) + } + + /// Appends a [`DualAxisExclusion`] processor as the next processing step, + /// only passing negative values that less than `negative_max` on both axes, + /// treating them as zeros. + /// + /// # Requirements + /// + /// - `negative_max` <= `0.0`. + /// + /// # Panics + /// + /// Panics if the requirements aren't met. + #[inline] + fn only_negative_unscaled(self, negative_max: f32) -> Self { + self.with_processor(DualAxisExclusion::only_negative_all(negative_max)) + } + + /// Appends a [`DualAxisExclusion`] processor as the next processing step, + /// only ignoring values within the range `[negative_max, positive_min]` on the X-axis, + /// treating them as zeros. + /// + /// # Requirements + /// + /// - `negative_max` <= `0.0` <= `positive_min`. + /// + /// # Panics + /// + /// Panics if the requirements aren't met. #[inline] - fn with_deadzone_x_unscaled(self, min: f32, max: f32) -> Self { - self.with_processor(DualAxisExclusion::only_x(min, max)) + fn with_deadzone_x_unscaled(self, negative_max: f32, positive_min: f32) -> Self { + self.with_processor(DualAxisExclusion::only_x(negative_max, positive_min)) } /// Appends a [`DualAxisExclusion`] processor as the next processing step, - /// only ignoring values within the dead zone range `[-threshold, threshold]` on the X-axis, + /// only ignoring values within the range `[-threshold, threshold]` on the X-axis, /// treating them as zeros. + /// + /// # Requirements + /// + /// - `threshold` >= `0.0`. + /// + /// # Panics + /// + /// Panics if the requirements aren't met. #[inline] fn with_deadzone_x_symmetric_unscaled(self, threshold: f32) -> Self { self.with_processor(DualAxisExclusion::symmetric_only_x(threshold)) } /// Appends a [`DualAxisExclusion`] processor as the next processing step, - /// only ignoring values within the dead zone range `[min, max]` on the Y-axis, + /// only excluding X values that less than or equal to `positive_min`, /// treating them as zeros. + /// + /// # Requirements + /// + /// - `positive_min` >= `0.0`. + /// + /// # Panics + /// + /// Panics if the requirements aren't met. + #[inline] + fn only_positive_x_unscaled(self, positive_min: f32) -> Self { + self.with_processor(DualAxisExclusion::only_positive_x(positive_min)) + } + + /// Appends a [`DualAxisExclusion`] processor as the next processing step, + /// only excluding X values that greater than or equal to `negative_max`, + /// treating them as zeros. + /// + /// # Requirements + /// + /// - `negative_max` <= `0.0`. + /// + /// # Panics + /// + /// Panics if the requirements aren't met. + #[inline] + fn only_negative_x_unscaled(self, negative_max: f32) -> Self { + self.with_processor(DualAxisExclusion::only_negative_x(negative_max)) + } + + /// Appends a [`DualAxisExclusion`] processor as the next processing step, + /// only ignoring values within the range `[negative_max, positive_min]` on the Y-axis, + /// treating them as zeros. + /// + /// # Requirements + /// + /// - `negative_max` <= `0.0` <= `positive_min`. + /// + /// # Panics + /// + /// Panics if the requirements aren't met. #[inline] - fn with_deadzone_y_unscaled(self, min: f32, max: f32) -> Self { - self.with_processor(DualAxisExclusion::only_y(min, max)) + fn with_deadzone_y_unscaled(self, negative_max: f32, positive_min: f32) -> Self { + self.with_processor(DualAxisExclusion::only_y(negative_max, positive_min)) } /// Appends a [`DualAxisExclusion`] processor as the next processing step, - /// only ignoring values within the dead zone range `[-threshold, threshold]` on the Y-axis, + /// only ignoring values within the range `[-threshold, threshold]` on the Y-axis, /// treating them as zeros. + /// + /// # Requirements + /// + /// - `threshold` >= `0.0`. + /// + /// # Panics + /// + /// Panics if the requirements aren't met. #[inline] fn with_deadzone_y_symmetric_unscaled(self, threshold: f32) -> Self { self.with_processor(DualAxisExclusion::symmetric_only_y(threshold)) } + /// Appends a [`DualAxisExclusion`] processor as the next processing step, + /// only excluding Y values that less than or equal to `positive_min`, + /// treating them as zeros. + /// + /// # Requirements + /// + /// - `positive_min` >= `0.0`. + /// + /// # Panics + /// + /// Panics if the requirements aren't met. + #[inline] + fn only_positive_y_unscaled(self, positive_min: f32) -> Self { + self.with_processor(DualAxisExclusion::only_positive_y(positive_min)) + } + + /// Appends a [`DualAxisExclusion`] processor as the next processing step, + /// only excluding Y values that greater than or equal to `negative_max`, + /// treating them as zeros. + /// + /// # Requirements + /// + /// - `negative_max` <= `0.0`. + /// + /// # Panics + /// + /// Panics if the requirements aren't met. + #[inline] + fn only_negative_y_unscaled(self, negative_max: f32) -> Self { + self.with_processor(DualAxisExclusion::only_negative_y(negative_max)) + } + /// Appends a [`CircleExclusion`] processor as the next processing step, /// ignoring values below a `min` magnitude, treating them as zeros. + /// + /// # Requirements + /// + /// - `min` >= `0.0`. + /// + /// # Panics + /// + /// Panics if the requirements aren't met. #[inline] fn with_circle_deadzone_unscaled(self, min: f32) -> Self { self.with_processor(CircleExclusion::new(min)) diff --git a/src/input_processing/dual_axis/range.rs b/src/input_processing/dual_axis/range.rs index 30285a40..d893da46 100644 --- a/src/input_processing/dual_axis/range.rs +++ b/src/input_processing/dual_axis/range.rs @@ -46,10 +46,10 @@ use crate::input_processing::single_axis::*; #[must_use] pub struct DualAxisBounds { /// The [`AxisBounds`] for the X-axis inputs. - pub(crate) bounds_x: AxisBounds, + pub bounds_x: AxisBounds, /// The [`AxisBounds`] for the Y-axis inputs. - pub(crate) bounds_y: AxisBounds, + pub bounds_y: AxisBounds, } impl DualAxisBounds { @@ -400,10 +400,10 @@ impl From for DualAxisProcessor { #[must_use] pub struct DualAxisExclusion { /// The [`AxisExclusion`] for the X-axis inputs. - pub(crate) exclusion_x: AxisExclusion, + pub exclusion_x: AxisExclusion, /// The [`AxisExclusion`] for the Y-axis inputs. - pub(crate) exclusion_y: AxisExclusion, + pub exclusion_y: AxisExclusion, } impl DualAxisExclusion { @@ -441,8 +441,7 @@ impl DualAxisExclusion { /// Panics if the requirements aren't met. #[inline] pub fn all(negative_max: f32, positive_min: f32) -> Self { - let range = (negative_max, positive_min); - Self::new(range, range) + AxisExclusion::new(negative_max, positive_min).extend_dual() } /// Creates a [`DualAxisExclusion`] that only ignores X values within the range `[negative_max, positive_min]`. @@ -509,7 +508,7 @@ impl DualAxisExclusion { #[doc(alias = "magnitude_all")] #[inline] pub fn symmetric_all(threshold: f32) -> Self { - Self::symmetric(threshold, threshold) + AxisExclusion::symmetric(threshold).extend_dual() } /// Creates a [`DualAxisExclusion`] that only ignores X values within the range `[-threshold, threshold]`. @@ -548,6 +547,136 @@ impl DualAxisExclusion { } } + /// Creates a [`DualAxisExclusion`] that only passes positive values that greater than `positive_min` on each axis. + /// + /// # Requirements + /// + /// - `positive_min` >= `0.0` on each axis. + /// + /// # Panics + /// + /// Panics if the requirements aren't met. + #[inline] + pub fn only_positive(x_positive_min: f32, y_positive_min: f32) -> Self { + Self { + exclusion_x: AxisExclusion::only_positive(x_positive_min), + exclusion_y: AxisExclusion::only_positive(y_positive_min), + } + } + + /// Creates a [`DualAxisExclusion`] that only passes positive values that greater than `positive_min` on both axes. + /// + /// # Requirements + /// + /// - `positive_min` >= `0.0`. + /// + /// # Panics + /// + /// Panics if the requirements aren't met. + #[inline] + pub fn only_positive_all(positive_min: f32) -> Self { + AxisExclusion::only_positive(positive_min).extend_dual() + } + + /// Creates a [`DualAxisExclusion`] that only passes positive X values that greater than `positive_min`. + /// + /// # Requirements + /// + /// - `positive_min` >= `0.0`. + /// + /// # Panics + /// + /// Panics if the requirements aren't met. + #[inline] + pub fn only_positive_x(positive_min: f32) -> Self { + Self { + exclusion_x: AxisExclusion::only_positive(positive_min), + ..Self::ZERO + } + } + + /// Creates a [`DualAxisExclusion`] that only passes positive Y values that greater than `positive_min`. + /// + /// # Requirements + /// + /// - `positive_min` >= `0.0`. + /// + /// # Panics + /// + /// Panics if the requirements aren't met. + #[inline] + pub fn only_positive_y(positive_min: f32) -> Self { + Self { + exclusion_y: AxisExclusion::only_positive(positive_min), + ..Self::ZERO + } + } + + /// Creates a [`DualAxisExclusion`] that only passes negative values that less than `negative_max` on each axis. + /// + /// # Requirements + /// + /// - `negative_max` <= `0.0` on each axis. + /// + /// # Panics + /// + /// Panics if the requirements aren't met. + #[inline] + pub fn only_negative(x_negative_max: f32, y_negative_max: f32) -> Self { + Self { + exclusion_x: AxisExclusion::only_negative(x_negative_max), + exclusion_y: AxisExclusion::only_negative(y_negative_max), + } + } + + /// Creates a [`DualAxisExclusion`] that only passes negative values that less than `negative_max` on both axes. + /// + /// # Requirements + /// + /// - `negative_max` <= `0.0`. + /// + /// # Panics + /// + /// Panics if the requirements aren't met. + #[inline] + pub fn only_negative_all(negative_max: f32) -> Self { + AxisExclusion::only_negative(negative_max).extend_dual() + } + + /// Creates a [`DualAxisExclusion`] that only passes negative X values that less than `negative_max`. + /// + /// # Requirements + /// + /// - `positive_min` >= `0.0`. + /// + /// # Panics + /// + /// Panics if the requirements aren't met. + #[inline] + pub fn only_negative_x(negative_max: f32) -> Self { + Self { + exclusion_x: AxisExclusion::only_negative(negative_max), + ..Self::ZERO + } + } + + /// Creates a [`DualAxisExclusion`] that only passes negative Y values that less than `negative_max`. + /// + /// # Requirements + /// + /// - `positive_min` >= `0.0`. + /// + /// # Panics + /// + /// Panics if the requirements aren't met. + #[inline] + pub fn only_negative_y(negative_max: f32) -> Self { + Self { + exclusion_y: AxisExclusion::only_negative(negative_max), + ..Self::ZERO + } + } + /// Returns the exclusion ranges for inputs along each axis. #[inline] pub fn exclusions(&self) -> (AxisExclusion, AxisExclusion) { @@ -703,10 +832,10 @@ impl From for DualAxisProcessor { #[must_use] pub struct DualAxisDeadZone { /// The [`AxisDeadZone`] for the X-axis inputs. - pub(crate) deadzone_x: AxisDeadZone, + pub deadzone_x: AxisDeadZone, /// The [`AxisDeadZone`] for the Y-axis inputs. - pub(crate) deadzone_y: AxisDeadZone, + pub deadzone_y: AxisDeadZone, } impl DualAxisDeadZone { @@ -744,8 +873,7 @@ impl DualAxisDeadZone { /// Panics if the requirements aren't met. #[inline] pub fn all(negative_max: f32, positive_min: f32) -> Self { - let range = (negative_max, positive_min); - Self::new(range, range) + AxisDeadZone::new(negative_max, positive_min).extend_dual() } /// Creates a [`DualAxisDeadZone`] that only excludes X values within the range `[negative_max, positive_min]`. @@ -812,7 +940,7 @@ impl DualAxisDeadZone { #[doc(alias = "magnitude_all")] #[inline] pub fn symmetric_all(threshold: f32) -> Self { - Self::symmetric(threshold, threshold) + AxisDeadZone::symmetric(threshold).extend_dual() } /// Creates a [`DualAxisDeadZone`] that only excludes X values within the range `[-threshold, threshold]`. @@ -851,6 +979,136 @@ impl DualAxisDeadZone { } } + /// Creates a [`DualAxisDeadZone`] that only passes positive values that greater than `positive_min` on each axis. + /// + /// # Requirements + /// + /// - `positive_min` >= `0.0` on each axis. + /// + /// # Panics + /// + /// Panics if the requirements aren't met. + #[inline] + pub fn only_positive(x_positive_min: f32, y_positive_min: f32) -> Self { + Self { + deadzone_x: AxisDeadZone::only_positive(x_positive_min), + deadzone_y: AxisDeadZone::only_positive(y_positive_min), + } + } + + /// Creates a [`DualAxisDeadZone`] that only passes positive values that greater than `positive_min` on both axes. + /// + /// # Requirements + /// + /// - `positive_min` >= `0.0`. + /// + /// # Panics + /// + /// Panics if the requirements aren't met. + #[inline] + pub fn only_positive_all(positive_min: f32) -> Self { + AxisDeadZone::only_positive(positive_min).extend_dual() + } + + /// Creates a [`DualAxisDeadZone`] that only excludes X values that less than or equal to `positive_min`. + /// + /// # Requirements + /// + /// - `positive_min` >= `0.0`. + /// + /// # Panics + /// + /// Panics if the requirements aren't met. + #[inline] + pub fn only_positive_x(positive_min: f32) -> Self { + Self { + deadzone_x: AxisDeadZone::only_positive(positive_min), + ..Self::ZERO + } + } + + /// Creates a [`DualAxisDeadZone`] that only excludes Y values that less than or equal to `positive_min`. + /// + /// # Requirements + /// + /// - `positive_min` >= `0.0`. + /// + /// # Panics + /// + /// Panics if the requirements aren't met. + #[inline] + pub fn only_positive_y(positive_min: f32) -> Self { + Self { + deadzone_y: AxisDeadZone::only_positive(positive_min), + ..Self::ZERO + } + } + + /// Creates a [`DualAxisDeadZone`] that only passes negative values that less than `negative_max` on each axis. + /// + /// # Requirements + /// + /// - `negative_max` <= `0.0` on each axis. + /// + /// # Panics + /// + /// Panics if the requirements aren't met. + #[inline] + pub fn only_negative(x_negative_max: f32, y_negative_max: f32) -> Self { + Self { + deadzone_x: AxisDeadZone::only_negative(x_negative_max), + deadzone_y: AxisDeadZone::only_negative(y_negative_max), + } + } + + /// Creates a [`DualAxisDeadZone`] that only passes negative values that less than `negative_max` on both axes. + /// + /// # Requirements + /// + /// - `negative_max` <= `0.0`. + /// + /// # Panics + /// + /// Panics if the requirements aren't met. + #[inline] + pub fn only_negative_all(negative_max: f32) -> Self { + AxisDeadZone::only_negative(negative_max).extend_dual() + } + + /// Creates a [`DualAxisDeadZone`] that only excludes X values that greater than or equal to `negative_max`. + /// + /// # Requirements + /// + /// - `positive_min` >= `0.0`. + /// + /// # Panics + /// + /// Panics if the requirements aren't met. + #[inline] + pub fn only_negative_x(negative_max: f32) -> Self { + Self { + deadzone_x: AxisDeadZone::only_negative(negative_max), + ..Self::ZERO + } + } + + /// Creates a [`DualAxisDeadZone`] that only excludes Y values that greater than or equal to `negative_max`. + /// + /// # Requirements + /// + /// - `positive_min` >= `0.0`. + /// + /// # Panics + /// + /// Panics if the requirements aren't met. + #[inline] + pub fn only_negative_y(negative_max: f32) -> Self { + Self { + deadzone_y: AxisDeadZone::only_negative(negative_max), + ..Self::ZERO + } + } + /// Returns the dead zones for inputs along each axis. #[inline] pub fn deadzones(&self) -> (AxisDeadZone, AxisDeadZone) { diff --git a/src/input_processing/single_axis/mod.rs b/src/input_processing/single_axis/mod.rs index e0248fb3..f0bd9172 100644 --- a/src/input_processing/single_axis/mod.rs +++ b/src/input_processing/single_axis/mod.rs @@ -159,17 +159,39 @@ pub trait WithAxisProcessingPipelineExt: Sized { } /// Appends an [`AxisBounds`] processor as the next processing step, - /// restricting values to a `threshold` magnitude. + /// restricting values within the range `[-threshold, threshold]`. #[inline] fn with_bounds_symmetric(self, threshold: f32) -> Self { self.with_processor(AxisBounds::symmetric(threshold)) } + /// Appends an [`AxisBounds`] processor as the next processing step, + /// restricting values to a minimum value. + #[inline] + fn at_least(self, min: f32) -> Self { + self.with_processor(AxisBounds::at_least(min)) + } + + /// Appends an [`AxisBounds`] processor as the next processing step, + /// restricting values to a maximum value. + #[inline] + fn at_most(self, max: f32) -> Self { + self.with_processor(AxisBounds::at_most(max)) + } + /// Appends an [`AxisDeadZone`] processor as the next processing step, /// excluding values within the dead zone range `[negative_max, positive_min]` on the axis, /// treating them as zeros, then normalizing non-excluded input values into the "live zone", /// the remaining range within the [`AxisBounds::magnitude(1.0)`](AxisBounds::default) /// after dead zone exclusion. + /// + /// # Requirements + /// + /// - `negative_max` <= `0.0` <= `positive_min`. + /// + /// # Panics + /// + /// Panics if the requirements aren't met. #[inline] fn with_deadzone(self, negative_max: f32, positive_min: f32) -> Self { self.with_processor(AxisDeadZone::new(negative_max, positive_min)) @@ -180,14 +202,62 @@ pub trait WithAxisProcessingPipelineExt: Sized { /// treating them as zeros, then normalizing non-excluded input values into the "live zone", /// the remaining range within the [`AxisBounds::magnitude(1.0)`](AxisBounds::default) /// after dead zone exclusion. + /// + /// # Requirements + /// + /// - `threshold` >= `0.0`. + /// + /// # Panics + /// + /// Panics if the requirements aren't met. #[inline] fn with_deadzone_symmetric(self, threshold: f32) -> Self { self.with_processor(AxisDeadZone::symmetric(threshold)) } + /// Appends an [`AxisDeadZone`] processor as the next processing step, + /// only passing positive values that greater than `positive_min` + /// and then normalizing them into the "live zone" range `[positive_min, 1.0]`. + /// + /// # Requirements + /// + /// - `positive_min` >= `0.0`. + /// + /// # Panics + /// + /// Panics if the requirements aren't met. + #[inline] + fn only_positive(self, positive_min: f32) -> Self { + self.with_processor(AxisDeadZone::only_positive(positive_min)) + } + + /// Appends an [`AxisDeadZone`] processor as the next processing step, + /// only passing negative values that less than `negative_max` + /// and then normalizing them into the "live zone" range `[-1.0, negative_max]`. + /// + /// # Requirements + /// + /// - `negative_max` <= `0.0`. + /// + /// # Panics + /// + /// Panics if the requirements aren't met. + #[inline] + fn only_negative(self, negative_max: f32) -> Self { + self.with_processor(AxisDeadZone::only_negative(negative_max)) + } + /// Appends an [`AxisExclusion`] processor as the next processing step, /// ignoring values within the dead zone range `[negative_max, positive_min]` on the axis, /// treating them as zeros. + /// + /// # Requirements + /// + /// - `negative_max` <= `0.0` <= `positive_min`. + /// + /// # Panics + /// + /// Panics if the requirements aren't met. #[inline] fn with_deadzone_unscaled(self, negative_max: f32, positive_min: f32) -> Self { self.with_processor(AxisExclusion::new(negative_max, positive_min)) @@ -196,10 +266,48 @@ pub trait WithAxisProcessingPipelineExt: Sized { /// Appends an [`AxisExclusion`] processor as the next processing step, /// ignoring values within the dead zone range `[-threshold, threshold]` on the axis, /// treating them as zeros. + /// + /// # Requirements + /// + /// - `threshold` >= `0.0`. + /// + /// # Panics + /// + /// Panics if the requirements aren't met. #[inline] fn with_deadzone_symmetric_unscaled(self, threshold: f32) -> Self { self.with_processor(AxisExclusion::symmetric(threshold)) } + + /// Appends an [`AxisExclusion`] processor as the next processing step, + /// only passing positive values that greater than `positive_min`. + /// + /// # Requirements + /// + /// - `positive_min` >= `0.0`. + /// + /// # Panics + /// + /// Panics if the requirements aren't met. + #[inline] + fn only_positive_unscaled(self, positive_min: f32) -> Self { + self.with_processor(AxisExclusion::only_positive(positive_min)) + } + + /// Appends an [`AxisExclusion`] processor as the next processing step, + /// only passing negative values that less than `negative_max`. + /// + /// # Requirements + /// + /// - `negative_max` <= `0.0`. + /// + /// # Panics + /// + /// Panics if the requirements aren't met. + #[inline] + fn only_negative_unscaled(self, negative_max: f32) -> Self { + self.with_processor(AxisExclusion::only_negative(negative_max)) + } } #[cfg(test)] diff --git a/src/input_processing/single_axis/range.rs b/src/input_processing/single_axis/range.rs index d173940f..3ac4ae75 100644 --- a/src/input_processing/single_axis/range.rs +++ b/src/input_processing/single_axis/range.rs @@ -236,6 +236,34 @@ impl AxisExclusion { Self::new(-threshold, threshold) } + /// Creates an [`AxisExclusion`] that only passes positive values that greater than `positive_min`. + /// + /// # Requirements + /// + /// - `positive_min` >= `0.0`. + /// + /// # Panics + /// + /// Panics if the requirements aren't met. + #[inline] + pub fn only_positive(positive_min: f32) -> Self { + Self::new(f32::NEG_INFINITY, positive_min) + } + + /// Creates an [`AxisExclusion`] that only passes negative values that less than `negative_max`. + /// + /// # Requirements + /// + /// - `negative_max` <= `0.0`. + /// + /// # Panics + /// + /// Panics if the requirements aren't met. + #[inline] + pub fn only_negative(negative_max: f32) -> Self { + Self::new(negative_max, f32::INFINITY) + } + /// Returns the minimum and maximum bounds. #[must_use] #[inline] @@ -387,8 +415,8 @@ impl AxisDeadZone { livezone_upper_recip: 1.0, }; - /// Creates an [`AxisDeadZone`] that excludes input values - /// within the given deadzone `[negative_max, positive_min]`. + /// Creates an [`AxisDeadZone`] that excludes input values within the range `[negative_max, positive_min]` + /// and then normalizes non-excluded input values into the valid range `[-1.0, 1.0]`. /// /// # Requirements /// @@ -407,7 +435,7 @@ impl AxisDeadZone { } } - /// Creates an [`AxisDeadZone`] that excludes input values below a `threshold` magnitude + /// Creates an [`AxisDeadZone`] that excludes input values within the range `[-threshold, threshold]` /// and then normalizes non-excluded input values into the valid range `[-1.0, 1.0]`. /// /// # Requirements @@ -420,7 +448,37 @@ impl AxisDeadZone { #[doc(alias = "magnitude")] #[inline] pub fn symmetric(threshold: f32) -> Self { - AxisDeadZone::new(-threshold, threshold) + Self::new(-threshold, threshold) + } + + /// Creates an [`AxisDeadZone`] that only passes positive values that greater than `positive_min` + /// and then normalizes them into the valid range `[-1.0, 1.0]`. + /// + /// # Requirements + /// + /// - `positive_min` >= `0.0`. + /// + /// # Panics + /// + /// Panics if the requirements aren't met. + #[inline] + pub fn only_positive(positive_min: f32) -> Self { + Self::new(f32::NEG_INFINITY, positive_min) + } + + /// Creates an [`AxisDeadZone`] that only passes negative values that less than `negative_max` + /// and then normalizes them into the valid range `[-1.0, 1.0]`. + /// + /// # Requirements + /// + /// - `negative_max` <= `0.0`. + /// + /// # Panics + /// + /// Panics if the requirements aren't met. + #[inline] + pub fn only_negative(negative_max: f32) -> Self { + Self::new(negative_max, f32::INFINITY) } /// Returns the [`AxisExclusion`] used by this deadzone.