1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
//! Serializable data types for network messages used by the game.

// use bevy::reflect::Reflect;
use numquant::{IntRange, Quantized};

use crate::prelude::*;

/// A newtype around [`Vec2`] that implements [`From<u16>`] and [`Into<u16>`] as a way to compress
/// user stick input for use in [`self::input::DenseInput`].
#[derive(Debug, Deref, DerefMut, Default)]
pub struct DenseMoveDirection(pub Vec2);

/// This is the specific [`Quantized`] type that we use to represent movement directions in
/// [`DenseMoveDirection`]. This encodes magnitude of direction, but sign is encoded separately.
type MoveDirQuant = Quantized<IntRange<u16, 0b11111, 0, 1>>;

impl From<u16> for DenseMoveDirection {
    fn from(bits: u16) -> Self {
        // maximum movement value representable, we use 6 bits to represent each movement direction.
        // Most significant is sign, and other 5 encode float value between 0 and
        let bit_length = 6;
        let quantized = 0b011111;
        let sign = 0b100000;
        // The first six bits represent the x movement
        let x_move_bits = bits & quantized;
        let x_move_sign = if bits & sign == 0 { 1.0 } else { -1.0 };
        // The second six bits represents the y movement
        let y_move_bits = (bits >> bit_length) & quantized;
        let y_move_sign = if (bits >> bit_length) & sign == 0 {
            1.0
        } else {
            -1.0
        };

        // Round near-zero values to zero
        let mut x = MoveDirQuant::from_raw(x_move_bits).to_f32();
        x *= x_move_sign;
        if x.abs() < 0.02 {
            x = 0.0;
        }
        let mut y = MoveDirQuant::from_raw(y_move_bits).to_f32();
        y *= y_move_sign;
        if y.abs() < 0.02 {
            y = 0.0;
        }

        DenseMoveDirection(Vec2::new(x, y))
    }
}

impl From<DenseMoveDirection> for u16 {
    fn from(dir: DenseMoveDirection) -> Self {
        let x_bits = MoveDirQuant::from_f32(dir.x.abs()).raw();
        let y_bits = MoveDirQuant::from_f32(dir.y.abs()).raw();
        let x_sign_bit = if dir.x.is_sign_positive() {
            0
        } else {
            0b100000
        };
        let y_sign_bit = if dir.y.is_sign_positive() {
            0
        } else {
            0b100000
        };

        (x_bits | x_sign_bit) | ((y_bits | y_sign_bit) << 6)
    }
}

impl From<u32> for DenseMoveDirection {
    fn from(bits: u32) -> Self {
        let bits_16 = bits as u16;
        bits_16.into()
    }
}

impl From<DenseMoveDirection> for u32 {
    fn from(dir: DenseMoveDirection) -> Self {
        let bits_16 = u16::from(dir);
        bits_16 as u32
    }
}

#[cfg(test)]
mod tests {
    use crate::prelude::proto::DenseMoveDirection;

    #[test]
    /// GGRS currently uses zero'd player input as prediction on first frame,
    /// so ensure our move direction representation is no input when built from
    /// 0 bits.
    pub fn zeroed_dense_move_dir() {
        let bits: u16 = 0;
        let dense_move_dir = DenseMoveDirection::from(bits);
        assert_eq!(dense_move_dir.0, glam::Vec2::ZERO);
    }
}