pub mod analog_stick {
pub const MIN: u8 = 0;
pub const CENTER: u8 = 128;
pub const MAX: u8 = 255;
pub const MIN_NORMALIZED: f32 = -1.0;
pub const CENTER_NORMALIZED: f32 = 0.0;
pub const MAX_NORMALIZED: f32 = 1.0;
pub const DEFAULT_DEADZONE: f32 = 0.15;
#[must_use]
pub fn normalize(raw: u8) -> f32 {
(f32::from(raw) - f32::from(CENTER)) / f32::from(CENTER)
}
#[must_use]
#[expect(
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
reason = "intentional conversion to u8 range 0-255"
)]
pub fn denormalize(normalized: f32) -> u8 {
((normalized * f32::from(CENTER)) + f32::from(CENTER)) as u8
}
#[must_use]
pub fn apply_deadzone(x: f32, y: f32, deadzone: f32) -> (f32, f32) {
let magnitude = (x * x + y * y).sqrt();
if magnitude < deadzone {
(0.0, 0.0)
} else {
let scale = (magnitude - deadzone) / (1.0 - deadzone);
(x * scale / magnitude, y * scale / magnitude)
}
}
}
pub mod trigger {
pub const MIN: u8 = 0;
pub const MAX: u8 = 255;
#[must_use]
pub fn normalize(raw: u8) -> f32 {
f32::from(raw) / f32::from(MAX)
}
#[must_use]
#[expect(
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
reason = "intentional conversion to u8 range 0-255"
)]
pub fn denormalize(normalized: f32) -> u8 {
(normalized * f32::from(MAX)) as u8
}
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
#[test]
fn test_analog_stick_normalize_center() {
assert_relative_eq!(analog_stick::normalize(128), 0.0);
}
#[test]
fn test_analog_stick_normalize_min() {
assert_relative_eq!(analog_stick::normalize(0), -1.0);
}
#[test]
fn test_analog_stick_normalize_max() {
assert_relative_eq!(analog_stick::normalize(255), 0.992_187_5); }
#[test]
fn test_analog_stick_denormalize_center() {
assert_eq!(analog_stick::denormalize(0.0), 128);
}
#[test]
fn test_analog_stick_denormalize_min() {
assert_eq!(analog_stick::denormalize(-1.0), 0);
}
#[test]
fn test_analog_stick_denormalize_max() {
assert_eq!(analog_stick::denormalize(1.0), 255);
}
#[test]
fn test_analog_stick_deadzone_inside() {
let (x, y) = analog_stick::apply_deadzone(0.1, 0.1, 0.15);
assert_relative_eq!(x, 0.0);
assert_relative_eq!(y, 0.0);
}
#[test]
fn test_analog_stick_deadzone_outside() {
let (x, y) = analog_stick::apply_deadzone(0.5, 0.0, 0.15);
assert!(x > 0.0);
assert_relative_eq!(y, 0.0);
}
#[test]
fn test_trigger_normalize_min() {
assert_relative_eq!(trigger::normalize(0), 0.0);
}
#[test]
fn test_trigger_normalize_max() {
assert_relative_eq!(trigger::normalize(255), 1.0);
}
#[test]
fn test_trigger_normalize_half() {
assert_relative_eq!(trigger::normalize(127), 0.498, epsilon = 0.01);
}
#[test]
fn test_trigger_denormalize_min() {
assert_eq!(trigger::denormalize(0.0), 0);
}
#[test]
fn test_trigger_denormalize_max() {
assert_eq!(trigger::denormalize(1.0), 255);
}
#[test]
fn test_trigger_denormalize_half() {
assert_eq!(trigger::denormalize(0.5), 127);
}
}