use std::hash::{Hash, Hasher};
use bevy::{math::FloatOrd, prelude::Reflect};
use serde::{Deserialize, Serialize};
pub use self::custom::*;
pub use self::range::*;
mod custom;
mod range;
#[must_use]
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Reflect, Serialize, Deserialize)]
pub enum AxisProcessor {
Digital,
Inverted,
Sensitivity(f32),
ValueBounds(AxisBounds),
Exclusion(AxisExclusion),
DeadZone(AxisDeadZone),
Custom(Box<dyn CustomAxisProcessor>),
}
impl AxisProcessor {
#[must_use]
#[inline]
pub fn process(&self, input_value: f32) -> f32 {
match self {
Self::Digital => {
if input_value == 0.0 {
0.0
} else {
input_value.signum()
}
}
Self::Inverted => -input_value,
Self::Sensitivity(sensitivity) => sensitivity * input_value,
Self::ValueBounds(bounds) => bounds.clamp(input_value),
Self::Exclusion(exclusion) => exclusion.exclude(input_value),
Self::DeadZone(deadzone) => deadzone.normalize(input_value),
Self::Custom(processor) => processor.process(input_value),
}
}
}
impl Eq for AxisProcessor {}
impl Hash for AxisProcessor {
fn hash<H: Hasher>(&self, state: &mut H) {
std::mem::discriminant(self).hash(state);
match self {
Self::Digital => {}
Self::Inverted => {}
Self::Sensitivity(sensitivity) => FloatOrd(*sensitivity).hash(state),
Self::ValueBounds(bounds) => bounds.hash(state),
Self::Exclusion(exclusion) => exclusion.hash(state),
Self::DeadZone(deadzone) => deadzone.hash(state),
Self::Custom(processor) => processor.hash(state),
}
}
}
pub trait WithAxisProcessingPipelineExt: Sized {
fn reset_processing_pipeline(self) -> Self;
fn replace_processing_pipeline(
self,
processors: impl IntoIterator<Item = AxisProcessor>,
) -> Self;
fn with_processor(self, processor: impl Into<AxisProcessor>) -> Self;
#[inline]
fn digital(self) -> Self {
self.with_processor(AxisProcessor::Digital)
}
#[inline]
fn inverted(self) -> Self {
self.with_processor(AxisProcessor::Inverted)
}
#[inline]
fn sensitivity(self, sensitivity: f32) -> Self {
self.with_processor(AxisProcessor::Sensitivity(sensitivity))
}
#[inline]
fn with_bounds(self, min: f32, max: f32) -> Self {
self.with_processor(AxisBounds::new(min, max))
}
#[inline]
fn with_bounds_symmetric(self, threshold: f32) -> Self {
self.with_processor(AxisBounds::symmetric(threshold))
}
#[inline]
fn with_deadzone(self, negative_max: f32, positive_min: f32) -> Self {
self.with_processor(AxisDeadZone::new(negative_max, positive_min))
}
#[inline]
fn with_deadzone_symmetric(self, threshold: f32) -> Self {
self.with_processor(AxisDeadZone::symmetric(threshold))
}
#[inline]
fn with_deadzone_unscaled(self, negative_max: f32, positive_min: f32) -> Self {
self.with_processor(AxisExclusion::new(negative_max, positive_min))
}
#[inline]
fn with_deadzone_symmetric_unscaled(self, threshold: f32) -> Self {
self.with_processor(AxisExclusion::symmetric(threshold))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_axis_inversion_processor() {
for value in -300..300 {
let value = value as f32 * 0.01;
assert_eq!(AxisProcessor::Inverted.process(value), -value);
assert_eq!(AxisProcessor::Inverted.process(-value), value);
}
}
#[test]
fn test_axis_sensitivity_processor() {
for value in -300..300 {
let value = value as f32 * 0.01;
for sensitivity in -300..300 {
let sensitivity = sensitivity as f32 * 0.01;
let processor = AxisProcessor::Sensitivity(sensitivity);
assert_eq!(processor.process(value), sensitivity * value);
}
}
}
}