diff --git a/gamebridge/src/main.rs b/gamebridge/src/main.rs index e4cebeb..426cd3e 100644 --- a/gamebridge/src/main.rs +++ b/gamebridge/src/main.rs @@ -20,6 +20,7 @@ pub(crate) struct State { controller: [u8; 4], last_got: Box, ok: bool, + frame: u64, } pub(crate) type MTState = Arc>; @@ -36,6 +37,7 @@ fn main() -> Result<()> { controller: [0; 4], last_got: Box::new(Instant::now()), ok: true, + frame: 0, }; Arc::new(RwLock::new(st)) @@ -48,6 +50,10 @@ fn main() -> Result<()> { spawn(move || twitch::run(st)); } + const LERP_TIME: f64 = 1.0; // 15 frames to lerp stick positions down to 0 + let mut xmax_frame: u64 = 0; + let mut ymax_frame: u64 = 0; + loop { let mut data = [0; 3]; debug!("waiting for vblank"); @@ -58,9 +64,56 @@ fn main() -> Result<()> { match str { "OK\n" => { let mut data = st.write().unwrap(); + data.frame += 1; + + let mut stickx = data.controller[2] as i8; + let mut sticky = data.controller[3] as i8; + + let dist = stick_distance(stickx, sticky); + if dist <= 10 { + stickx = 0; + sticky = 0; + xmax_frame = 0; + ymax_frame = 0; + } + + stickx = match stickx { + 0 => stickx, + 127 => { + xmax_frame = data.frame; + stickx - 10 + }, + -128 => { + xmax_frame = data.frame; + stickx + 10 + }, + _ => { + let t = (data.frame - xmax_frame) as f64 / (LERP_TIME as f64); + lerp(stickx, 0, t) + }, + }; + + sticky = match stickx { + 0 => sticky, + 127 => { + ymax_frame = data.frame; + sticky - 10 + }, + -128 => { + ymax_frame = data.frame; + sticky + 10 + }, + _ => { + let t = (data.frame - ymax_frame) as f64 / (LERP_TIME as f64); + lerp(sticky, 0, t) + }, + }; + input.write(&data.controller)?; data.controller[0] = 0; data.controller[1] = 0; + data.controller[2] = stickx as u8; + data.controller[3] = sticky as u8; } "BYE" => { warn!("asked to exit by the game"); @@ -73,3 +126,42 @@ fn main() -> Result<()> { }; } } + +fn lerp(start: i8, end: i8, t: f64) -> i8 { + (start as f64 * (1.0 - t) + (end as f64) * t) as i8 +} + +fn stick_distance(x: i8, y: i8) -> i8 { + let x = (x as f64).powi(2); + let y = (y as f64).powi(2); + (x + y).sqrt() as i8 +} + +#[cfg(test)] +mod test { + #[test] + fn lerp_scale() { + for case in [(0.1, 10), (0.5, 31)].iter() { + let t = case.0; + let start = 127.0 * t; + assert_eq!(crate::lerp(start as i8, 0, t), case.1); + } + } + + #[test] + fn stick_distance() { + for case in [ + (0, 0, 0), + (127, 0, 127), + (64, 64, 90), + (-64, 64, 90), + (-64, -64, 90), + ] + .iter() + { + let x = case.0; + let y = case.1; + assert_eq!(crate::stick_distance(x, y), case.2); + } + } +} diff --git a/gamebridge/src/twitch.rs b/gamebridge/src/twitch.rs index 3cc3138..795e1bc 100644 --- a/gamebridge/src/twitch.rs +++ b/gamebridge/src/twitch.rs @@ -70,8 +70,6 @@ async fn run_loop( } wait_and_join(&mut control, &mut dispatcher, channels).await; - let mut stickx: i8 = 0; - let mut sticky: i8 = 0; loop { tokio::select! { @@ -84,6 +82,14 @@ async fn run_loop( Some(msg) = pmsg.next() => { let mut hi = HiButtons::NONE; let mut lo = LoButtons::NONE; + let mut stickx: i8 = 0; + let mut sticky: i8 = 0; + + { + let data = st.read().unwrap(); + stickx = data.controller[2] as i8; + sticky = data.controller[3] as i8; + } let mut data = msg.data.to_string(); let data = data.to_ascii_lowercase();