oxihuman_morph/
blink_control.rs1#[allow(dead_code)]
4pub struct BlinkParams {
5 pub blink_duration: f32,
7 pub blink_rate_hz: f32,
9 pub variation: f32,
11 pub close_speed: f32,
13 pub open_speed: f32,
15}
16
17#[allow(dead_code)]
18#[derive(Clone, Copy, PartialEq, Debug)]
19pub enum BlinkPhase {
20 Open,
21 Closing,
22 Closed,
23 Opening,
24}
25
26#[allow(dead_code)]
27pub struct BlinkState {
28 pub phase: BlinkPhase,
29 pub phase_time: f32,
30 pub next_blink_time: f32,
31 pub left_eye_open: f32,
33 pub right_eye_open: f32,
34 pub synchronized: bool,
35 pub enabled: bool,
36}
37
38#[allow(dead_code)]
44pub fn lcg_next(state: &mut u64) -> f32 {
45 *state = state
47 .wrapping_mul(6_364_136_223_846_793_005)
48 .wrapping_add(1_442_695_040_888_963_407);
49 let bits = (*state >> 33) as u32;
51 (bits as f32) / (u32::MAX as f32 + 1.0)
52}
53
54#[allow(dead_code)]
59pub fn default_blink_params() -> BlinkParams {
60 BlinkParams {
61 blink_duration: 0.15,
62 blink_rate_hz: 0.25,
63 variation: 0.3,
64 close_speed: 1.0,
65 open_speed: 1.0,
66 }
67}
68
69#[allow(dead_code)]
70pub fn new_blink_state(sync: bool) -> BlinkState {
71 BlinkState {
72 phase: BlinkPhase::Open,
73 phase_time: 0.0,
74 next_blink_time: 3.0,
75 left_eye_open: 1.0,
76 right_eye_open: 1.0,
77 synchronized: sync,
78 enabled: true,
79 }
80}
81
82#[allow(dead_code)]
87pub fn update_blink(state: &mut BlinkState, params: &BlinkParams, dt: f32, rng: &mut u64) {
88 if !state.enabled {
89 return;
90 }
91
92 state.phase_time += dt;
93
94 match state.phase {
95 BlinkPhase::Open => {
96 state.left_eye_open = 1.0;
97 state.right_eye_open = 1.0;
98 if state.phase_time >= state.next_blink_time {
99 state.phase = BlinkPhase::Closing;
100 state.phase_time = 0.0;
101 }
102 }
103 BlinkPhase::Closing => {
104 let half = params.blink_duration * 0.5 / params.close_speed.max(0.001);
105 let t = (state.phase_time / half).min(1.0);
106 let v = 1.0 - t;
107 state.left_eye_open = v;
108 if state.synchronized {
109 state.right_eye_open = v;
110 } else {
111 state.right_eye_open = (v + lcg_next(rng) * 0.1).min(1.0);
112 }
113 if state.phase_time >= half {
114 state.phase = BlinkPhase::Closed;
115 state.phase_time = 0.0;
116 state.left_eye_open = 0.0;
117 state.right_eye_open = 0.0;
118 }
119 }
120 BlinkPhase::Closed => {
121 let hold = params.blink_duration * 0.1;
122 if state.phase_time >= hold {
123 state.phase = BlinkPhase::Opening;
124 state.phase_time = 0.0;
125 }
126 }
127 BlinkPhase::Opening => {
128 let half = params.blink_duration * 0.5 / params.open_speed.max(0.001);
129 let t = (state.phase_time / half).min(1.0);
130 state.left_eye_open = t;
131 if state.synchronized {
132 state.right_eye_open = t;
133 } else {
134 state.right_eye_open = (t + lcg_next(rng) * 0.05).min(1.0);
135 }
136 if state.phase_time >= half {
137 state.phase = BlinkPhase::Open;
138 state.phase_time = 0.0;
139 state.left_eye_open = 1.0;
140 state.right_eye_open = 1.0;
141 let base = 1.0 / params.blink_rate_hz.max(0.001);
143 let var = (lcg_next(rng) * 2.0 - 1.0) * params.variation * base;
144 state.next_blink_time = (base + var).max(0.1);
145 }
146 }
147 }
148}
149
150#[allow(dead_code)]
155pub fn trigger_manual_blink(state: &mut BlinkState) {
156 if state.phase == BlinkPhase::Open {
157 state.phase = BlinkPhase::Closing;
158 state.phase_time = 0.0;
159 }
160}
161
162#[allow(dead_code)]
163pub fn blink_value(state: &BlinkState) -> f32 {
164 (state.left_eye_open + state.right_eye_open) * 0.5
165}
166
167#[allow(dead_code)]
168pub fn set_blink_synchronized(state: &mut BlinkState, sync: bool) {
169 state.synchronized = sync;
170}
171
172#[allow(dead_code)]
173pub fn enable_blink(state: &mut BlinkState) {
174 state.enabled = true;
175}
176
177#[allow(dead_code)]
178pub fn disable_blink(state: &mut BlinkState) {
179 state.enabled = false;
180}
181
182#[allow(dead_code)]
183pub fn is_blinking(state: &BlinkState) -> bool {
184 state.phase != BlinkPhase::Open
185}
186
187#[allow(dead_code)]
188pub fn force_open_eyes(state: &mut BlinkState) {
189 state.phase = BlinkPhase::Open;
190 state.phase_time = 0.0;
191 state.left_eye_open = 1.0;
192 state.right_eye_open = 1.0;
193}
194
195#[allow(dead_code)]
196pub fn force_close_eyes(state: &mut BlinkState) {
197 state.phase = BlinkPhase::Closed;
198 state.phase_time = 0.0;
199 state.left_eye_open = 0.0;
200 state.right_eye_open = 0.0;
201}
202
203#[allow(dead_code)]
204pub fn blink_speed_for_emotion(emotion: &str) -> f32 {
205 match emotion {
206 "surprised" => 2.0,
207 "tired" => 0.5,
208 _ => 1.0,
209 }
210}
211
212#[cfg(test)]
217mod tests {
218 use super::*;
219
220 #[test]
221 fn test_default_params() {
222 let p = default_blink_params();
223 assert!(p.blink_duration > 0.0);
224 assert!(p.blink_rate_hz > 0.0);
225 assert!(p.variation >= 0.0);
226 assert!(p.close_speed > 0.0);
227 assert!(p.open_speed > 0.0);
228 }
229
230 #[test]
231 fn test_new_state_defaults() {
232 let state = new_blink_state(true);
233 assert_eq!(state.phase, BlinkPhase::Open);
234 assert!((state.left_eye_open - 1.0).abs() < 1e-6);
235 assert!((state.right_eye_open - 1.0).abs() < 1e-6);
236 assert!(state.synchronized);
237 assert!(state.enabled);
238 }
239
240 #[test]
241 fn test_update_advances_phase() {
242 let params = default_blink_params();
243 let mut state = new_blink_state(true);
244 let mut rng: u64 = 42;
246 let trigger_time = state.next_blink_time + 0.01;
247 update_blink(&mut state, ¶ms, trigger_time, &mut rng);
248 assert_ne!(state.phase, BlinkPhase::Open);
249 }
250
251 #[test]
252 fn test_trigger_manual_blink() {
253 let mut state = new_blink_state(true);
254 assert_eq!(state.phase, BlinkPhase::Open);
255 trigger_manual_blink(&mut state);
256 assert_eq!(state.phase, BlinkPhase::Closing);
257 }
258
259 #[test]
260 fn test_blink_value_open() {
261 let state = new_blink_state(true);
262 assert!((blink_value(&state) - 1.0).abs() < 1e-6);
263 }
264
265 #[test]
266 fn test_blink_value_closed() {
267 let mut state = new_blink_state(true);
268 force_close_eyes(&mut state);
269 assert!((blink_value(&state)).abs() < 1e-6);
270 }
271
272 #[test]
273 fn test_enable_disable() {
274 let mut state = new_blink_state(false);
275 disable_blink(&mut state);
276 assert!(!state.enabled);
277 enable_blink(&mut state);
278 assert!(state.enabled);
279 }
280
281 #[test]
282 fn test_is_blinking_open() {
283 let state = new_blink_state(true);
284 assert!(!is_blinking(&state));
285 }
286
287 #[test]
288 fn test_is_blinking_closing() {
289 let mut state = new_blink_state(true);
290 trigger_manual_blink(&mut state);
291 assert!(is_blinking(&state));
292 }
293
294 #[test]
295 fn test_force_open_eyes() {
296 let mut state = new_blink_state(true);
297 force_close_eyes(&mut state);
298 force_open_eyes(&mut state);
299 assert_eq!(state.phase, BlinkPhase::Open);
300 assert!((state.left_eye_open - 1.0).abs() < 1e-6);
301 }
302
303 #[test]
304 fn test_force_close_eyes() {
305 let mut state = new_blink_state(true);
306 force_close_eyes(&mut state);
307 assert_eq!(state.phase, BlinkPhase::Closed);
308 assert!((state.left_eye_open).abs() < 1e-6);
309 }
310
311 #[test]
312 fn test_blink_speed_for_emotion() {
313 assert!((blink_speed_for_emotion("surprised") - 2.0).abs() < 1e-6);
314 assert!((blink_speed_for_emotion("tired") - 0.5).abs() < 1e-6);
315 assert!((blink_speed_for_emotion("neutral") - 1.0).abs() < 1e-6);
316 }
317
318 #[test]
319 fn test_lcg_next_range() {
320 let mut rng: u64 = 123456;
321 for _ in 0..1000 {
322 let v = lcg_next(&mut rng);
323 assert!((0.0..1.0).contains(&v), "lcg_next out of [0,1): {}", v);
324 }
325 }
326
327 #[test]
328 fn test_set_blink_synchronized() {
329 let mut state = new_blink_state(true);
330 set_blink_synchronized(&mut state, false);
331 assert!(!state.synchronized);
332 set_blink_synchronized(&mut state, true);
333 assert!(state.synchronized);
334 }
335
336 #[test]
337 fn test_full_blink_cycle() {
338 let params = default_blink_params();
339 let mut state = new_blink_state(true);
340 let mut rng: u64 = 999;
341 trigger_manual_blink(&mut state);
342 for _ in 0..100 {
344 update_blink(&mut state, ¶ms, 0.01, &mut rng);
345 }
346 assert_eq!(state.phase, BlinkPhase::Open);
348 }
349}