subtr_actor/stats/calculators/
continuous_ball_control.rs1use super::*;
2
3#[derive(Debug, Clone, Default, PartialEq)]
4pub struct ContinuousBallControlState {
5 pub completed_sequences: Vec<CompletedBallControlSequence<BallCarryKind>>,
6}
7
8#[derive(Debug, Clone, Copy)]
9pub struct ContinuousBallControlSample<K> {
10 pub kind: K,
11 pub player_position: glam::Vec3,
12 pub horizontal_gap: f32,
13 pub vertical_gap: f32,
14 pub speed: f32,
15}
16
17#[derive(Debug, Clone)]
18pub struct ContinuousBallControlCandidate<K> {
19 pub player_id: PlayerId,
20 pub is_team_0: bool,
21 pub touch_count: u32,
22 pub sample: ContinuousBallControlSample<K>,
23}
24
25#[derive(Debug, Clone, PartialEq)]
26pub struct CompletedBallControlSequence<K> {
27 pub player_id: PlayerId,
28 pub is_team_0: bool,
29 pub kind: K,
30 pub start_frame: usize,
31 pub end_frame: usize,
32 pub start_time: f32,
33 pub end_time: f32,
34 pub duration: f32,
35 pub straight_line_distance: f32,
36 pub path_distance: f32,
37 pub average_horizontal_gap: f32,
38 pub average_vertical_gap: f32,
39 pub average_speed: f32,
40 pub start_position: glam::Vec3,
41 pub end_position: glam::Vec3,
42 pub touch_count: u32,
43}
44
45#[derive(Debug, Clone)]
46struct ActiveBallControlSequence<K> {
47 player_id: PlayerId,
48 is_team_0: bool,
49 kind: K,
50 start_frame: usize,
51 last_frame: usize,
52 start_time: f32,
53 last_time: f32,
54 start_position: glam::Vec3,
55 last_position: glam::Vec3,
56 duration: f32,
57 path_distance: f32,
58 horizontal_gap_integral: f32,
59 vertical_gap_integral: f32,
60 speed_integral: f32,
61 touch_count: u32,
62}
63
64#[derive(Debug, Clone)]
65pub struct ContinuousBallControlTracker<K> {
66 active_sequence: Option<ActiveBallControlSequence<K>>,
67}
68
69impl<K> Default for ContinuousBallControlTracker<K> {
70 fn default() -> Self {
71 Self {
72 active_sequence: None,
73 }
74 }
75}
76
77impl<K> ContinuousBallControlTracker<K>
78where
79 K: Copy + PartialEq,
80{
81 fn begin_sequence(
82 frame: &FrameInfo,
83 candidate: ContinuousBallControlCandidate<K>,
84 ) -> ActiveBallControlSequence<K> {
85 let sample = candidate.sample;
86 ActiveBallControlSequence {
87 player_id: candidate.player_id,
88 is_team_0: candidate.is_team_0,
89 kind: sample.kind,
90 start_frame: frame.frame_number.saturating_sub(1),
91 last_frame: frame.frame_number,
92 start_time: (frame.time - frame.dt).max(0.0),
93 last_time: frame.time,
94 start_position: sample.player_position,
95 last_position: sample.player_position,
96 duration: frame.dt,
97 path_distance: 0.0,
98 horizontal_gap_integral: sample.horizontal_gap * frame.dt,
99 vertical_gap_integral: sample.vertical_gap * frame.dt,
100 speed_integral: sample.speed * frame.dt,
101 touch_count: candidate.touch_count,
102 }
103 }
104
105 fn extend_sequence(
106 active_sequence: &mut ActiveBallControlSequence<K>,
107 frame: &FrameInfo,
108 sample: ContinuousBallControlSample<K>,
109 touch_count: u32,
110 ) {
111 active_sequence.duration += frame.dt;
112 active_sequence.path_distance += sample
113 .player_position
114 .distance(active_sequence.last_position);
115 active_sequence.last_position = sample.player_position;
116 active_sequence.last_time = frame.time;
117 active_sequence.last_frame = frame.frame_number;
118 active_sequence.horizontal_gap_integral += sample.horizontal_gap * frame.dt;
119 active_sequence.vertical_gap_integral += sample.vertical_gap * frame.dt;
120 active_sequence.speed_integral += sample.speed * frame.dt;
121 active_sequence.touch_count += touch_count;
122 }
123
124 fn complete_sequence(
125 active_sequence: ActiveBallControlSequence<K>,
126 ) -> CompletedBallControlSequence<K> {
127 CompletedBallControlSequence {
128 player_id: active_sequence.player_id,
129 is_team_0: active_sequence.is_team_0,
130 kind: active_sequence.kind,
131 start_frame: active_sequence.start_frame,
132 end_frame: active_sequence.last_frame,
133 start_time: active_sequence.start_time,
134 end_time: active_sequence.last_time,
135 duration: active_sequence.duration,
136 straight_line_distance: active_sequence
137 .start_position
138 .truncate()
139 .distance(active_sequence.last_position.truncate()),
140 path_distance: active_sequence.path_distance,
141 average_horizontal_gap: active_sequence.horizontal_gap_integral
142 / active_sequence.duration,
143 average_vertical_gap: active_sequence.vertical_gap_integral / active_sequence.duration,
144 average_speed: active_sequence.speed_integral / active_sequence.duration,
145 start_position: active_sequence.start_position,
146 end_position: active_sequence.last_position,
147 touch_count: active_sequence.touch_count,
148 }
149 }
150
151 fn finish_active_sequence<F>(
152 &mut self,
153 min_duration_for_kind: F,
154 ) -> Option<CompletedBallControlSequence<K>>
155 where
156 F: Fn(K) -> f32,
157 {
158 let active_sequence = self.active_sequence.take()?;
159 if active_sequence.duration < min_duration_for_kind(active_sequence.kind) {
160 return None;
161 }
162 Some(Self::complete_sequence(active_sequence))
163 }
164
165 pub fn update<F>(
166 &mut self,
167 frame: &FrameInfo,
168 candidate: Option<ContinuousBallControlCandidate<K>>,
169 min_duration_for_kind: F,
170 ) -> Vec<CompletedBallControlSequence<K>>
171 where
172 F: Fn(K) -> f32 + Copy,
173 {
174 let mut completed = Vec::new();
175 let Some(candidate) = candidate else {
176 if let Some(sequence) = self.finish_active_sequence(min_duration_for_kind) {
177 completed.push(sequence);
178 }
179 return completed;
180 };
181
182 let same_sequence = self
183 .active_sequence
184 .as_ref()
185 .is_some_and(|active_sequence| {
186 active_sequence.player_id == candidate.player_id
187 && active_sequence.kind == candidate.sample.kind
188 });
189
190 if same_sequence {
191 if let Some(active_sequence) = self.active_sequence.as_mut() {
192 Self::extend_sequence(
193 active_sequence,
194 frame,
195 candidate.sample,
196 candidate.touch_count,
197 );
198 }
199 } else {
200 if let Some(sequence) = self.finish_active_sequence(min_duration_for_kind) {
201 completed.push(sequence);
202 }
203 self.active_sequence = Some(Self::begin_sequence(frame, candidate));
204 }
205
206 completed
207 }
208
209 pub fn finish<F>(&mut self, min_duration_for_kind: F) -> Option<CompletedBallControlSequence<K>>
210 where
211 F: Fn(K) -> f32,
212 {
213 self.finish_active_sequence(min_duration_for_kind)
214 }
215}