dreamwell-engine 1.0.0

Dreamwell pure-logic engine library — transforms, hierarchy, canon pipeline, spatial math, hashing, tile rules, validation, waymark schema, material/lighting descriptors. No SpacetimeDB dependency.
Documentation
// Axis — float axis abstraction with dead zones.
// Handles analog sticks, triggers, scroll wheels, and composite digital axes (WASD → [-1, 1]).

/// A single-axis float value with dead zone filtering.
#[derive(Debug, Clone, Copy)]
pub struct AxisValue {
    raw: f32,
    dead_zone: f32,
}

impl AxisValue {
    pub fn new(dead_zone: f32) -> Self {
        Self {
            raw: 0.0,
            dead_zone: dead_zone.abs(),
        }
    }

    /// Set the raw axis value.
    pub fn set(&mut self, value: f32) {
        self.raw = value;
    }

    /// Get the filtered value (0.0 if within dead zone).
    pub fn value(&self) -> f32 {
        if self.raw.abs() <= self.dead_zone {
            0.0
        } else {
            self.raw
        }
    }

    /// Get the raw unfiltered value.
    pub fn raw(&self) -> f32 {
        self.raw
    }

    /// Whether the axis is outside its dead zone.
    pub fn active(&self) -> bool {
        self.raw.abs() > self.dead_zone
    }

    /// Reset to zero.
    pub fn reset(&mut self) {
        self.raw = 0.0;
    }
}

impl Default for AxisValue {
    fn default() -> Self {
        Self::new(0.0)
    }
}

/// A 2D axis pair (e.g., left stick, mouse delta, WASD composite).
#[derive(Debug, Clone, Copy)]
pub struct AxisPair {
    pub x: AxisValue,
    pub y: AxisValue,
}

impl AxisPair {
    pub fn new(dead_zone: f32) -> Self {
        Self {
            x: AxisValue::new(dead_zone),
            y: AxisValue::new(dead_zone),
        }
    }

    /// Set both axes.
    pub fn set(&mut self, x: f32, y: f32) {
        self.x.set(x);
        self.y.set(y);
    }

    /// Get filtered values as [x, y].
    pub fn value(&self) -> [f32; 2] {
        [self.x.value(), self.y.value()]
    }

    /// Whether either axis is outside its dead zone.
    pub fn active(&self) -> bool {
        self.x.active() || self.y.active()
    }

    /// Squared magnitude (avoids sqrt).
    pub fn magnitude_sq(&self) -> f32 {
        let [x, y] = self.value();
        x * x + y * y
    }

    /// Normalized direction (unit vector). Returns [0, 0] if magnitude is zero.
    pub fn normalized(&self) -> [f32; 2] {
        let [x, y] = self.value();
        let mag_sq = x * x + y * y;
        if mag_sq < 1e-10 {
            [0.0, 0.0]
        } else {
            let inv_mag = 1.0 / mag_sq.sqrt();
            [x * inv_mag, y * inv_mag]
        }
    }

    /// Reset both axes to zero.
    pub fn reset(&mut self) {
        self.x.reset();
        self.y.reset();
    }
}

impl Default for AxisPair {
    fn default() -> Self {
        Self::new(0.0)
    }
}

/// Build a composite axis value from two digital buttons (e.g., A/D → x axis).
/// Returns -1.0, 0.0, or 1.0.
pub fn digital_axis(negative: bool, positive: bool) -> f32 {
    match (negative, positive) {
        (true, false) => -1.0,
        (false, true) => 1.0,
        _ => 0.0,
    }
}

/// Build a composite axis pair from four digital buttons (WASD pattern).
pub fn digital_axis_pair(left: bool, right: bool, down: bool, up: bool) -> [f32; 2] {
    [digital_axis(left, right), digital_axis(down, up)]
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn axis_value_dead_zone() {
        let mut axis = AxisValue::new(0.1);
        axis.set(0.05);
        assert_eq!(axis.value(), 0.0);
        assert!(!axis.active());

        axis.set(0.5);
        assert_eq!(axis.value(), 0.5);
        assert!(axis.active());
    }

    #[test]
    fn axis_value_negative() {
        let mut axis = AxisValue::new(0.1);
        axis.set(-0.8);
        assert_eq!(axis.value(), -0.8);
        assert!(axis.active());
    }

    #[test]
    fn axis_value_reset() {
        let mut axis = AxisValue::new(0.0);
        axis.set(1.0);
        axis.reset();
        assert_eq!(axis.value(), 0.0);
    }

    #[test]
    fn axis_pair_basic() {
        let mut pair = AxisPair::new(0.0);
        pair.set(0.5, -0.3);
        assert_eq!(pair.value(), [0.5, -0.3]);
        assert!(pair.active());
    }

    #[test]
    fn axis_pair_normalized() {
        let mut pair = AxisPair::new(0.0);
        pair.set(3.0, 4.0);
        let [nx, ny] = pair.normalized();
        assert!((nx - 0.6).abs() < 1e-5);
        assert!((ny - 0.8).abs() < 1e-5);
    }

    #[test]
    fn axis_pair_zero_normalized() {
        let pair = AxisPair::new(0.0);
        assert_eq!(pair.normalized(), [0.0, 0.0]);
    }

    #[test]
    fn digital_axis_values() {
        assert_eq!(digital_axis(false, false), 0.0);
        assert_eq!(digital_axis(true, false), -1.0);
        assert_eq!(digital_axis(false, true), 1.0);
        assert_eq!(digital_axis(true, true), 0.0); // cancel out
    }

    #[test]
    fn digital_axis_pair_wasd() {
        // W=up, A=left, S=down, D=right
        let [x, y] = digital_axis_pair(true, false, false, true); // A + W
        assert_eq!(x, -1.0);
        assert_eq!(y, 1.0);
    }
}