use super::action::InputAction;
#[derive(Debug, Clone)]
pub struct InputFrame {
pub actions: Vec<InputAction>,
pub movement: [f32; 2],
pub look_delta: [f32; 2],
pub scroll_delta: f32,
pub cursor_position: [f32; 2],
pub cursor_delta: [f32; 2],
pub timestamp: f32,
}
impl InputFrame {
pub fn has_action(&self, action: InputAction) -> bool {
self.actions.contains(&action)
}
pub fn has_movement(&self) -> bool {
self.movement[0] != 0.0 || self.movement[1] != 0.0
}
pub fn has_look(&self) -> bool {
self.look_delta[0] != 0.0 || self.look_delta[1] != 0.0
}
pub fn has_scroll(&self) -> bool {
self.scroll_delta != 0.0
}
}
impl Default for InputFrame {
fn default() -> Self {
Self {
actions: Vec::new(),
movement: [0.0; 2],
look_delta: [0.0; 2],
scroll_delta: 0.0,
cursor_position: [0.0; 2],
cursor_delta: [0.0; 2],
timestamp: 0.0,
}
}
}
pub struct InputFrameBuilder {
actions: Vec<InputAction>,
movement: [f32; 2],
look_delta: [f32; 2],
scroll_delta: f32,
cursor_position: [f32; 2],
cursor_delta: [f32; 2],
timestamp: f32,
}
impl InputFrameBuilder {
pub fn new(timestamp: f32) -> Self {
Self {
actions: Vec::new(),
movement: [0.0; 2],
look_delta: [0.0; 2],
scroll_delta: 0.0,
cursor_position: [0.0; 2],
cursor_delta: [0.0; 2],
timestamp,
}
}
pub fn action(mut self, action: InputAction) -> Self {
if !self.actions.contains(&action) {
self.actions.push(action);
}
self
}
pub fn movement(mut self, x: f32, y: f32) -> Self {
self.movement = [x, y];
self
}
pub fn look(mut self, dx: f32, dy: f32) -> Self {
self.look_delta = [dx, dy];
self
}
pub fn scroll(mut self, delta: f32) -> Self {
self.scroll_delta = delta;
self
}
pub fn cursor(mut self, x: f32, y: f32) -> Self {
self.cursor_position = [x, y];
self
}
pub fn cursor_delta(mut self, dx: f32, dy: f32) -> Self {
self.cursor_delta = [dx, dy];
self
}
pub fn build(self) -> InputFrame {
InputFrame {
actions: self.actions,
movement: self.movement,
look_delta: self.look_delta,
scroll_delta: self.scroll_delta,
cursor_position: self.cursor_position,
cursor_delta: self.cursor_delta,
timestamp: self.timestamp,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_frame() {
let frame = InputFrame::default();
assert!(!frame.has_movement());
assert!(!frame.has_look());
assert!(!frame.has_scroll());
assert!(frame.actions.is_empty());
}
#[test]
fn builder_actions() {
let frame = InputFrameBuilder::new(0.0)
.action(InputAction::MoveForward)
.action(InputAction::Sprint)
.build();
assert!(frame.has_action(InputAction::MoveForward));
assert!(frame.has_action(InputAction::Sprint));
assert!(!frame.has_action(InputAction::Jump));
}
#[test]
fn builder_no_duplicate_actions() {
let frame = InputFrameBuilder::new(0.0)
.action(InputAction::Attack)
.action(InputAction::Attack)
.build();
assert_eq!(frame.actions.len(), 1);
}
#[test]
fn builder_movement() {
let frame = InputFrameBuilder::new(0.0).movement(-1.0, 1.0).build();
assert!(frame.has_movement());
assert_eq!(frame.movement, [-1.0, 1.0]);
}
#[test]
fn builder_look_and_scroll() {
let frame = InputFrameBuilder::new(0.0).look(5.0, -3.0).scroll(1.5).build();
assert!(frame.has_look());
assert!(frame.has_scroll());
assert_eq!(frame.look_delta, [5.0, -3.0]);
assert_eq!(frame.scroll_delta, 1.5);
}
#[test]
fn builder_cursor() {
let frame = InputFrameBuilder::new(1.0)
.cursor(100.0, 200.0)
.cursor_delta(2.0, -1.0)
.build();
assert_eq!(frame.cursor_position, [100.0, 200.0]);
assert_eq!(frame.cursor_delta, [2.0, -1.0]);
assert_eq!(frame.timestamp, 1.0);
}
#[test]
fn full_frame_build() {
let frame = InputFrameBuilder::new(0.016)
.action(InputAction::MoveForward)
.action(InputAction::Sprint)
.movement(0.0, 1.0)
.look(0.0, 0.0)
.scroll(-0.5)
.cursor(640.0, 480.0)
.cursor_delta(0.0, 0.0)
.build();
assert!(frame.has_action(InputAction::MoveForward));
assert!(frame.has_action(InputAction::Sprint));
assert!(frame.has_movement());
assert!(!frame.has_look());
assert!(frame.has_scroll());
assert_eq!(frame.timestamp, 0.016);
}
}
#[derive(Debug, Clone)]
pub struct InputPacket {
pub movement: [f32; 2],
pub camera_yaw: f32,
pub jump: bool,
pub interact: bool,
pub sprint: bool,
pub gather: bool,
pub emit_strength: f32,
pub coherence_target: f32,
pub dt: f32,
pub timestamp: f64,
pub tick: u64,
pub grounded: bool,
}
impl InputPacket {
pub fn idle(dt: f32, tick: u64) -> Self {
Self {
movement: [0.0; 2],
camera_yaw: 0.0,
jump: false,
interact: false,
sprint: false,
gather: false,
emit_strength: 0.0,
coherence_target: 1.0,
dt,
timestamp: 0.0,
tick,
grounded: true,
}
}
pub fn from_frame(
frame: &InputFrame,
camera_yaw: f32,
dt: f32,
timestamp: f64,
tick: u64,
grounded: bool,
coherence_target: f32,
gather: bool,
emit_strength: f32,
) -> Self {
Self {
movement: frame.movement,
camera_yaw,
jump: frame.has_action(InputAction::Jump),
interact: frame.has_action(InputAction::Interact),
sprint: frame.has_action(InputAction::Sprint),
gather,
emit_strength,
coherence_target,
dt,
timestamp,
tick,
grounded,
}
}
pub fn actions_as_u8(&self) -> Vec<u8> {
let mut out = Vec::with_capacity(4);
if self.movement[1] > 0.0 {
out.push(0);
} if self.movement[1] < 0.0 {
out.push(1);
} if self.movement[0] < 0.0 {
out.push(2);
} if self.movement[0] > 0.0 {
out.push(3);
} if self.sprint {
out.push(6);
} if self.jump {
out.push(8);
} if self.interact {
out.push(16);
} if self.gather {
out.push(100);
} out
}
pub fn digest(&self) -> [u8; 32] {
let mut hasher = blake3::Hasher::new();
hasher.update(&self.movement[0].to_le_bytes());
hasher.update(&self.movement[1].to_le_bytes());
hasher.update(&self.camera_yaw.to_le_bytes());
hasher.update(&[
self.jump as u8,
self.interact as u8,
self.sprint as u8,
self.gather as u8,
]);
hasher.update(&self.emit_strength.to_le_bytes());
hasher.update(&self.coherence_target.to_le_bytes());
hasher.update(&self.dt.to_le_bytes());
hasher.update(&self.timestamp.to_le_bytes());
hasher.update(&self.tick.to_le_bytes());
hasher.update(&[self.grounded as u8]);
*hasher.finalize().as_bytes()
}
pub fn has_movement(&self) -> bool {
self.movement[0] != 0.0 || self.movement[1] != 0.0
}
}
#[cfg(test)]
mod input_packet_tests {
use super::*;
#[test]
fn idle_packet_is_zero() {
let p = InputPacket::idle(1.0 / 60.0, 0);
assert!(!p.has_movement());
assert!(!p.jump);
assert!(!p.sprint);
assert!(p.grounded);
assert_eq!(p.coherence_target, 1.0);
}
#[test]
fn from_frame_maps_actions() {
let frame = InputFrameBuilder::new(0.0)
.action(InputAction::Jump)
.action(InputAction::Sprint)
.movement(0.5, 1.0)
.build();
let p = InputPacket::from_frame(&frame, 0.0, 0.016, 1.0, 1, true, 0.8, false, 0.0);
assert!(p.jump);
assert!(p.sprint);
assert!(!p.interact);
assert_eq!(p.movement, [0.5, 1.0]);
assert_eq!(p.coherence_target, 0.8);
}
#[test]
fn actions_as_u8_encodes_movement() {
let mut p = InputPacket::idle(0.016, 0);
p.movement = [0.0, 1.0]; p.sprint = true;
let actions = p.actions_as_u8();
assert!(actions.contains(&0)); assert!(actions.contains(&6)); assert!(!actions.contains(&8)); }
#[test]
fn digest_is_nonzero() {
let p = InputPacket::idle(0.016, 0);
let d = p.digest();
assert_ne!(d, [0u8; 32]);
}
#[test]
fn digest_changes_with_input() {
let p1 = InputPacket::idle(0.016, 0);
let mut p2 = InputPacket::idle(0.016, 0);
p2.jump = true;
assert_ne!(p1.digest(), p2.digest());
}
}