oxihuman_viewer/
motion_sample.rs1#![allow(dead_code)]
4
5use std::f32::consts::FRAC_PI_4;
8
9pub const MOTION_SAMPLE_CAPACITY: usize = 16;
10
11#[allow(dead_code)]
12#[derive(Debug, Clone, Copy)]
13pub struct MotionSampleEntry {
14 pub velocity: [f32; 2],
15 pub timestamp: f32,
16}
17
18#[allow(dead_code)]
19#[derive(Debug, Clone)]
20pub struct MotionSampleBuffer {
21 pub entries: [Option<MotionSampleEntry>; MOTION_SAMPLE_CAPACITY],
22 pub head: usize,
23 pub len: usize,
24}
25
26#[allow(dead_code)]
27pub fn new_motion_sample_buffer() -> MotionSampleBuffer {
28 MotionSampleBuffer {
29 entries: [None; MOTION_SAMPLE_CAPACITY],
30 head: 0,
31 len: 0,
32 }
33}
34
35#[allow(dead_code)]
36pub fn msb_push(buf: &mut MotionSampleBuffer, velocity: [f32; 2], timestamp: f32) {
37 let idx = buf.head % MOTION_SAMPLE_CAPACITY;
38 buf.entries[idx] = Some(MotionSampleEntry {
39 velocity,
40 timestamp,
41 });
42 buf.head = (buf.head + 1) % MOTION_SAMPLE_CAPACITY;
43 if buf.len < MOTION_SAMPLE_CAPACITY {
44 buf.len += 1;
45 }
46}
47
48#[allow(dead_code)]
49pub fn msb_clear(buf: &mut MotionSampleBuffer) {
50 buf.entries = [None; MOTION_SAMPLE_CAPACITY];
51 buf.head = 0;
52 buf.len = 0;
53}
54
55#[allow(dead_code)]
56pub fn msb_count(buf: &MotionSampleBuffer) -> usize {
57 buf.len
58}
59
60#[allow(dead_code)]
61pub fn msb_is_empty(buf: &MotionSampleBuffer) -> bool {
62 buf.len == 0
63}
64
65#[allow(dead_code)]
66pub fn msb_average_velocity(buf: &MotionSampleBuffer) -> [f32; 2] {
67 if buf.len == 0 {
68 return [0.0, 0.0];
69 }
70 let mut sx = 0.0f32;
71 let mut sy = 0.0f32;
72 for e in buf.entries.iter().flatten() {
73 sx += e.velocity[0];
74 sy += e.velocity[1];
75 }
76 [sx / buf.len as f32, sy / buf.len as f32]
77}
78
79#[allow(dead_code)]
80pub fn msb_peak_speed(buf: &MotionSampleBuffer) -> f32 {
81 buf.entries
82 .iter()
83 .flatten()
84 .map(|e| (e.velocity[0] * e.velocity[0] + e.velocity[1] * e.velocity[1]).sqrt())
85 .fold(0.0f32, f32::max)
86}
87
88#[allow(dead_code)]
89pub fn msb_speed_angle_rad(buf: &MotionSampleBuffer) -> f32 {
90 let s = msb_peak_speed(buf);
91 if s > 0.0 {
92 (1.0 / s).atan().min(FRAC_PI_4)
93 } else {
94 0.0
95 }
96}
97
98#[allow(dead_code)]
99pub fn msb_to_json(buf: &MotionSampleBuffer) -> String {
100 let avg = msb_average_velocity(buf);
101 format!(
102 "{{\"count\":{},\"avg_vel\":[{:.4},{:.4}]}}",
103 msb_count(buf),
104 avg[0],
105 avg[1]
106 )
107}
108
109#[cfg(test)]
110mod tests {
111 use super::*;
112 #[test]
113 fn new_is_empty() {
114 assert!(msb_is_empty(&new_motion_sample_buffer()));
115 }
116 #[test]
117 fn push_increments_count() {
118 let mut b = new_motion_sample_buffer();
119 msb_push(&mut b, [1.0, 0.0], 0.0);
120 assert_eq!(msb_count(&b), 1);
121 }
122 #[test]
123 fn clear_empties() {
124 let mut b = new_motion_sample_buffer();
125 msb_push(&mut b, [1.0, 0.0], 0.0);
126 msb_clear(&mut b);
127 assert!(msb_is_empty(&b));
128 }
129 #[test]
130 fn wraps_at_capacity() {
131 let mut b = new_motion_sample_buffer();
132 for i in 0..MOTION_SAMPLE_CAPACITY + 2 {
133 msb_push(&mut b, [i as f32, 0.0], i as f32);
134 }
135 assert_eq!(msb_count(&b), MOTION_SAMPLE_CAPACITY);
136 }
137 #[test]
138 fn average_velocity_empty_zero() {
139 let b = new_motion_sample_buffer();
140 let avg = msb_average_velocity(&b);
141 assert!(avg[0].abs() < 1e-6 && avg[1].abs() < 1e-6);
142 }
143 #[test]
144 fn average_velocity_one_entry() {
145 let mut b = new_motion_sample_buffer();
146 msb_push(&mut b, [4.0, 2.0], 0.0);
147 let avg = msb_average_velocity(&b);
148 assert!((avg[0] - 4.0).abs() < 1e-5);
149 }
150 #[test]
151 fn peak_speed_correct() {
152 let mut b = new_motion_sample_buffer();
153 msb_push(&mut b, [3.0, 4.0], 0.0);
154 assert!((msb_peak_speed(&b) - 5.0).abs() < 1e-4);
155 }
156 #[test]
157 fn speed_angle_nonneg() {
158 assert!(msb_speed_angle_rad(&new_motion_sample_buffer()) >= 0.0);
159 }
160 #[test]
161 fn to_json_has_count() {
162 assert!(msb_to_json(&new_motion_sample_buffer()).contains("\"count\""));
163 }
164 #[test]
165 fn capacity_is_sixteen() {
166 assert_eq!(MOTION_SAMPLE_CAPACITY, 16);
167 }
168}