1use glam::Vec2;
5
6#[derive(Clone, Copy, Debug)]
9pub struct StagedClick {
10 pub world: Vec2,
11 pub dp: [f32; 2],
12}
13
14#[derive(Clone, Copy, Debug, Default)]
18pub struct AimTracker {
19 hover: Option<Vec2>,
20 hover_px: Option<Vec2>,
21 cursor_px: Option<Vec2>,
22 cursor_seq: u64,
23 hover_seq: u64,
24 hover_px_seq: u64,
25 input_seq: u64,
26}
27
28impl AimTracker {
29 pub const STALE_AFTER: u64 = 30;
31
32 pub fn cursor_move(&mut self, phys_px: Vec2) {
36 self.input_seq += 1;
37 self.cursor_seq = self.input_seq;
38 self.cursor_px = Some(phys_px);
39 }
40
41 pub fn stage_hover(&mut self, world: Vec2, screen: [f32; 2]) {
44 self.input_seq += 1;
45 self.hover_seq = self.input_seq;
46 self.hover = Some(world);
47 self.hover_px_seq = self.input_seq;
48 self.hover_px = Some(Vec2::new(screen[0], screen[1]));
49 }
50
51 pub fn live_px(&self) -> Option<Vec2> {
55 if self.hover_px_seq > 0
56 && self.input_seq.wrapping_sub(self.hover_px_seq) <= Self::STALE_AFTER
57 {
58 return self.hover_px;
59 }
60 if self.cursor_seq > 0 && self.input_seq.wrapping_sub(self.cursor_seq) <= Self::STALE_AFTER
61 {
62 return self.cursor_px;
63 }
64 None
65 }
66
67 pub fn baked(&self) -> Option<Vec2> {
70 self.hover
71 }
72}
73
74#[cfg(test)]
75mod tests {
76 use super::*;
77
78 #[test]
79 fn fresh_px_wins_stale_px_yields_none() {
80 let mut t = AimTracker::default();
81 assert_eq!(t.live_px(), None);
82 t.stage_hover(Vec2::new(10.0, 10.0), [0.0, 0.0]);
83 t.cursor_move(Vec2::new(100.0, 100.0));
84 assert_eq!(t.live_px(), Some(Vec2::new(0.0, 0.0)));
85 for _ in 0..40 {
86 t.stage_hover(Vec2::new(10.0, 10.0), [50.0, 50.0]);
87 }
88 assert_eq!(t.live_px(), Some(Vec2::new(50.0, 50.0)));
89 assert_eq!(t.baked(), Some(Vec2::new(10.0, 10.0)));
90 }
91}