use std::hash::{Hash, Hasher};
use bevy::{
math::FloatOrd,
prelude::{BVec2, Reflect, Vec2},
};
use serde::{Deserialize, Serialize};
use crate::input_processing::AxisProcessor;
pub use self::circle::*;
pub use self::custom::*;
pub use self::range::*;
mod circle;
mod custom;
mod range;
#[must_use]
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
pub enum DualAxisProcessor {
Digital,
Inverted(DualAxisInverted),
Sensitivity(DualAxisSensitivity),
ValueBounds(DualAxisBounds),
Exclusion(DualAxisExclusion),
DeadZone(DualAxisDeadZone),
CircleBounds(CircleBounds),
CircleExclusion(CircleExclusion),
CircleDeadZone(CircleDeadZone),
Custom(Box<dyn CustomDualAxisProcessor>),
}
impl DualAxisProcessor {
#[must_use]
#[inline]
pub fn process(&self, input_value: Vec2) -> Vec2 {
match self {
Self::Digital => Vec2::new(
AxisProcessor::Digital.process(input_value.x),
AxisProcessor::Digital.process(input_value.y),
),
Self::Inverted(inversion) => inversion.invert(input_value),
Self::Sensitivity(sensitivity) => sensitivity.scale(input_value),
Self::ValueBounds(bounds) => bounds.clamp(input_value),
Self::Exclusion(exclusion) => exclusion.exclude(input_value),
Self::DeadZone(deadzone) => deadzone.normalize(input_value),
Self::CircleBounds(bounds) => bounds.clamp(input_value),
Self::CircleExclusion(exclusion) => exclusion.exclude(input_value),
Self::CircleDeadZone(deadzone) => deadzone.normalize(input_value),
Self::Custom(processor) => processor.process(input_value),
}
}
}
pub trait WithDualAxisProcessingPipelineExt: Sized {
fn reset_processing_pipeline(self) -> Self;
fn replace_processing_pipeline(
self,
processors: impl IntoIterator<Item = DualAxisProcessor>,
) -> Self;
fn with_processor(self, processor: impl Into<DualAxisProcessor>) -> Self;
#[inline]
fn digital(self) -> Self {
self.with_processor(DualAxisProcessor::Digital)
}
#[inline]
fn inverted(self) -> Self {
self.with_processor(DualAxisInverted::ALL)
}
#[inline]
fn inverted_x(self) -> Self {
self.with_processor(DualAxisInverted::ONLY_X)
}
#[inline]
fn inverted_y(self) -> Self {
self.with_processor(DualAxisInverted::ONLY_Y)
}
#[inline]
fn sensitivity(self, sensitivity: f32) -> Self {
self.with_processor(DualAxisSensitivity::all(sensitivity))
}
#[inline]
fn sensitivity_x(self, sensitivity: f32) -> Self {
self.with_processor(DualAxisSensitivity::only_x(sensitivity))
}
#[inline]
fn sensitivity_y(self, sensitivity: f32) -> Self {
self.with_processor(DualAxisSensitivity::only_y(sensitivity))
}
#[inline]
fn with_bounds(self, min: f32, max: f32) -> Self {
self.with_processor(DualAxisBounds::all(min, max))
}
#[inline]
fn with_bounds_symmetric(self, threshold: f32) -> Self {
self.with_processor(DualAxisBounds::symmetric_all(threshold))
}
#[inline]
fn with_bounds_x(self, min: f32, max: f32) -> Self {
self.with_processor(DualAxisBounds::only_x(min, max))
}
#[inline]
fn with_bounds_x_symmetric(self, threshold: f32) -> Self {
self.with_processor(DualAxisBounds::symmetric_all(threshold))
}
#[inline]
fn with_bounds_y(self, min: f32, max: f32) -> Self {
self.with_processor(DualAxisBounds::only_y(min, max))
}
#[inline]
fn with_bounds_y_symmetric(self, threshold: f32) -> Self {
self.with_processor(DualAxisBounds::symmetric_all(threshold))
}
#[inline]
fn at_least(self, min: f32) -> Self {
self.with_processor(DualAxisBounds::at_least_all(min))
}
#[inline]
fn at_least_only_x(self, min: f32) -> Self {
self.with_processor(DualAxisBounds::at_least_only_x(min))
}
#[inline]
fn at_least_only_y(self, min: f32) -> Self {
self.with_processor(DualAxisBounds::at_least_only_y(min))
}
#[inline]
fn at_most(self, min: f32) -> Self {
self.with_processor(DualAxisBounds::at_most_all(min))
}
#[inline]
fn at_most_only_x(self, min: f32) -> Self {
self.with_processor(DualAxisBounds::at_most_only_x(min))
}
#[inline]
fn at_most_only_y(self, min: f32) -> Self {
self.with_processor(DualAxisBounds::at_most_only_y(min))
}
#[inline]
fn with_circle_bounds(self, max: f32) -> Self {
self.with_processor(CircleBounds::new(max))
}
#[inline]
fn with_deadzone(self, negative_max: f32, positive_min: f32) -> Self {
self.with_processor(DualAxisDeadZone::all(negative_max, positive_min))
}
#[inline]
fn with_deadzone_symmetric(self, threshold: f32) -> Self {
self.with_processor(DualAxisDeadZone::symmetric_all(threshold))
}
#[inline]
fn only_positive(self, positive_min: f32) -> Self {
self.with_processor(DualAxisDeadZone::only_positive_all(positive_min))
}
#[inline]
fn only_negative(self, negative_max: f32) -> Self {
self.with_processor(DualAxisDeadZone::only_negative_all(negative_max))
}
#[inline]
fn with_deadzone_x(self, negative_max: f32, positive_min: f32) -> Self {
self.with_processor(DualAxisDeadZone::only_x(negative_max, positive_min))
}
#[inline]
fn with_deadzone_x_symmetric(self, threshold: f32) -> Self {
self.with_processor(DualAxisDeadZone::symmetric_only_x(threshold))
}
#[inline]
fn only_positive_x(self, positive_min: f32) -> Self {
self.with_processor(DualAxisDeadZone::only_positive_x(positive_min))
}
#[inline]
fn only_negative_x(self, negative_max: f32) -> Self {
self.with_processor(DualAxisDeadZone::only_negative_x(negative_max))
}
#[inline]
fn with_deadzone_y(self, negative_max: f32, positive_min: f32) -> Self {
self.with_processor(DualAxisDeadZone::only_y(negative_max, positive_min))
}
#[inline]
fn with_deadzone_y_symmetric(self, threshold: f32) -> Self {
self.with_processor(DualAxisDeadZone::symmetric_only_y(threshold))
}
#[inline]
fn only_positive_y(self, positive_min: f32) -> Self {
self.with_processor(DualAxisDeadZone::only_positive_y(positive_min))
}
#[inline]
fn only_negative_y(self, negative_max: f32) -> Self {
self.with_processor(DualAxisDeadZone::only_negative_y(negative_max))
}
#[inline]
fn with_circle_deadzone(self, min: f32) -> Self {
self.with_processor(CircleDeadZone::new(min))
}
#[inline]
fn with_deadzone_unscaled(self, negative_max: f32, positive_min: f32) -> Self {
self.with_processor(DualAxisExclusion::all(negative_max, positive_min))
}
#[inline]
fn with_deadzone_symmetric_unscaled(self, threshold: f32) -> Self {
self.with_processor(DualAxisExclusion::symmetric_all(threshold))
}
#[inline]
fn only_positive_unscaled(self, positive_min: f32) -> Self {
self.with_processor(DualAxisExclusion::only_positive_all(positive_min))
}
#[inline]
fn only_negative_unscaled(self, negative_max: f32) -> Self {
self.with_processor(DualAxisExclusion::only_negative_all(negative_max))
}
#[inline]
fn with_deadzone_x_unscaled(self, negative_max: f32, positive_min: f32) -> Self {
self.with_processor(DualAxisExclusion::only_x(negative_max, positive_min))
}
#[inline]
fn with_deadzone_x_symmetric_unscaled(self, threshold: f32) -> Self {
self.with_processor(DualAxisExclusion::symmetric_only_x(threshold))
}
#[inline]
fn only_positive_x_unscaled(self, positive_min: f32) -> Self {
self.with_processor(DualAxisExclusion::only_positive_x(positive_min))
}
#[inline]
fn only_negative_x_unscaled(self, negative_max: f32) -> Self {
self.with_processor(DualAxisExclusion::only_negative_x(negative_max))
}
#[inline]
fn with_deadzone_y_unscaled(self, negative_max: f32, positive_min: f32) -> Self {
self.with_processor(DualAxisExclusion::only_y(negative_max, positive_min))
}
#[inline]
fn with_deadzone_y_symmetric_unscaled(self, threshold: f32) -> Self {
self.with_processor(DualAxisExclusion::symmetric_only_y(threshold))
}
#[inline]
fn only_positive_y_unscaled(self, positive_min: f32) -> Self {
self.with_processor(DualAxisExclusion::only_positive_y(positive_min))
}
#[inline]
fn only_negative_y_unscaled(self, negative_max: f32) -> Self {
self.with_processor(DualAxisExclusion::only_negative_y(negative_max))
}
#[inline]
fn with_circle_deadzone_unscaled(self, min: f32) -> Self {
self.with_processor(CircleExclusion::new(min))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Reflect, Serialize, Deserialize)]
#[must_use]
pub struct DualAxisInverted(Vec2);
impl DualAxisInverted {
pub const ALL: Self = Self(Vec2::NEG_ONE);
pub const ONLY_X: Self = Self(Vec2::new(-1.0, 1.0));
pub const ONLY_Y: Self = Self(Vec2::new(1.0, -1.0));
#[must_use]
#[inline]
pub fn inverted(&self) -> BVec2 {
self.0.cmpeq(Vec2::NEG_ONE)
}
#[must_use]
#[inline]
pub fn invert(&self, input_value: Vec2) -> Vec2 {
self.0 * input_value
}
}
impl From<DualAxisInverted> for DualAxisProcessor {
fn from(value: DualAxisInverted) -> Self {
Self::Inverted(value)
}
}
impl Eq for DualAxisInverted {}
impl Hash for DualAxisInverted {
fn hash<H: Hasher>(&self, state: &mut H) {
FloatOrd(self.0.x).hash(state);
FloatOrd(self.0.y).hash(state);
}
}
#[derive(Debug, Clone, Copy, PartialEq, Reflect, Serialize, Deserialize)]
#[must_use]
pub struct DualAxisSensitivity(pub(crate) Vec2);
impl DualAxisSensitivity {
#[inline]
pub const fn new(sensitivity_x: f32, sensitivity_y: f32) -> Self {
Self(Vec2::new(sensitivity_x, sensitivity_y))
}
#[inline]
pub const fn all(sensitivity: f32) -> Self {
Self::new(sensitivity, sensitivity)
}
#[inline]
pub const fn only_x(sensitivity: f32) -> Self {
Self::new(sensitivity, 1.0)
}
#[inline]
pub const fn only_y(sensitivity: f32) -> Self {
Self::new(1.0, sensitivity)
}
#[must_use]
#[inline]
pub fn sensitivities(&self) -> Vec2 {
self.0
}
#[must_use]
#[inline]
pub fn scale(&self, input_value: Vec2) -> Vec2 {
self.0 * input_value
}
}
impl From<DualAxisSensitivity> for DualAxisProcessor {
fn from(value: DualAxisSensitivity) -> Self {
Self::Sensitivity(value)
}
}
impl Eq for DualAxisSensitivity {}
impl Hash for DualAxisSensitivity {
fn hash<H: Hasher>(&self, state: &mut H) {
FloatOrd(self.0.x).hash(state);
FloatOrd(self.0.y).hash(state);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_dual_axis_inverted() {
let all = DualAxisInverted::ALL;
assert_eq!(all.inverted(), BVec2::TRUE);
let only_x = DualAxisInverted::ONLY_X;
assert_eq!(only_x.inverted(), BVec2::new(true, false));
let only_y = DualAxisInverted::ONLY_Y;
assert_eq!(only_y.inverted(), BVec2::new(false, true));
for x in -300..300 {
let x = x as f32 * 0.01;
for y in -300..300 {
let y = y as f32 * 0.01;
let value = Vec2::new(x, y);
let processor = DualAxisProcessor::Inverted(all);
assert_eq!(DualAxisProcessor::from(all), processor);
assert_eq!(processor.process(value), all.invert(value));
assert_eq!(all.invert(value), -value);
assert_eq!(all.invert(-value), value);
let processor = DualAxisProcessor::Inverted(only_x);
assert_eq!(DualAxisProcessor::from(only_x), processor);
assert_eq!(processor.process(value), only_x.invert(value));
assert_eq!(only_x.invert(value), Vec2::new(-x, y));
assert_eq!(only_x.invert(-value), Vec2::new(x, -y));
let processor = DualAxisProcessor::Inverted(only_y);
assert_eq!(DualAxisProcessor::from(only_y), processor);
assert_eq!(processor.process(value), only_y.invert(value));
assert_eq!(only_y.invert(value), Vec2::new(x, -y));
assert_eq!(only_y.invert(-value), Vec2::new(-x, y));
}
}
}
#[test]
fn test_dual_axis_sensitivity() {
for x in -300..300 {
let x = x as f32 * 0.01;
for y in -300..300 {
let y = y as f32 * 0.01;
let value = Vec2::new(x, y);
let sensitivity = x;
let all = DualAxisSensitivity::all(sensitivity);
let processor = DualAxisProcessor::Sensitivity(all);
assert_eq!(DualAxisProcessor::from(all), processor);
assert_eq!(processor.process(value), all.scale(value));
assert_eq!(all.sensitivities(), Vec2::splat(sensitivity));
assert_eq!(all.scale(value), sensitivity * value);
let only_x = DualAxisSensitivity::only_x(sensitivity);
let processor = DualAxisProcessor::Sensitivity(only_x);
assert_eq!(DualAxisProcessor::from(only_x), processor);
assert_eq!(processor.process(value), only_x.scale(value));
assert_eq!(only_x.sensitivities(), Vec2::new(sensitivity, 1.0));
assert_eq!(only_x.scale(value).x, x * sensitivity);
assert_eq!(only_x.scale(value).y, y);
let only_y = DualAxisSensitivity::only_y(sensitivity);
let processor = DualAxisProcessor::Sensitivity(only_y);
assert_eq!(DualAxisProcessor::from(only_y), processor);
assert_eq!(processor.process(value), only_y.scale(value));
assert_eq!(only_y.sensitivities(), Vec2::new(1.0, sensitivity));
assert_eq!(only_y.scale(value).x, x);
assert_eq!(only_y.scale(value).y, y * sensitivity);
let sensitivity2 = y;
let separate = DualAxisSensitivity::new(sensitivity, sensitivity2);
let processor = DualAxisProcessor::Sensitivity(separate);
assert_eq!(DualAxisProcessor::from(separate), processor);
assert_eq!(processor.process(value), separate.scale(value));
assert_eq!(
separate.sensitivities(),
Vec2::new(sensitivity, sensitivity2)
);
assert_eq!(separate.scale(value).x, x * sensitivity);
assert_eq!(separate.scale(value).y, y * sensitivity2);
}
}
}
}