#[allow(dead_code)]
pub struct BlinkParams {
pub blink_duration: f32,
pub blink_rate_hz: f32,
pub variation: f32,
pub close_speed: f32,
pub open_speed: f32,
}
#[allow(dead_code)]
#[derive(Clone, Copy, PartialEq, Debug)]
pub enum BlinkPhase {
Open,
Closing,
Closed,
Opening,
}
#[allow(dead_code)]
pub struct BlinkState {
pub phase: BlinkPhase,
pub phase_time: f32,
pub next_blink_time: f32,
pub left_eye_open: f32,
pub right_eye_open: f32,
pub synchronized: bool,
pub enabled: bool,
}
#[allow(dead_code)]
pub fn lcg_next(state: &mut u64) -> f32 {
*state = state
.wrapping_mul(6_364_136_223_846_793_005)
.wrapping_add(1_442_695_040_888_963_407);
let bits = (*state >> 33) as u32;
(bits as f32) / (u32::MAX as f32 + 1.0)
}
#[allow(dead_code)]
pub fn default_blink_params() -> BlinkParams {
BlinkParams {
blink_duration: 0.15,
blink_rate_hz: 0.25,
variation: 0.3,
close_speed: 1.0,
open_speed: 1.0,
}
}
#[allow(dead_code)]
pub fn new_blink_state(sync: bool) -> BlinkState {
BlinkState {
phase: BlinkPhase::Open,
phase_time: 0.0,
next_blink_time: 3.0,
left_eye_open: 1.0,
right_eye_open: 1.0,
synchronized: sync,
enabled: true,
}
}
#[allow(dead_code)]
pub fn update_blink(state: &mut BlinkState, params: &BlinkParams, dt: f32, rng: &mut u64) {
if !state.enabled {
return;
}
state.phase_time += dt;
match state.phase {
BlinkPhase::Open => {
state.left_eye_open = 1.0;
state.right_eye_open = 1.0;
if state.phase_time >= state.next_blink_time {
state.phase = BlinkPhase::Closing;
state.phase_time = 0.0;
}
}
BlinkPhase::Closing => {
let half = params.blink_duration * 0.5 / params.close_speed.max(0.001);
let t = (state.phase_time / half).min(1.0);
let v = 1.0 - t;
state.left_eye_open = v;
if state.synchronized {
state.right_eye_open = v;
} else {
state.right_eye_open = (v + lcg_next(rng) * 0.1).min(1.0);
}
if state.phase_time >= half {
state.phase = BlinkPhase::Closed;
state.phase_time = 0.0;
state.left_eye_open = 0.0;
state.right_eye_open = 0.0;
}
}
BlinkPhase::Closed => {
let hold = params.blink_duration * 0.1;
if state.phase_time >= hold {
state.phase = BlinkPhase::Opening;
state.phase_time = 0.0;
}
}
BlinkPhase::Opening => {
let half = params.blink_duration * 0.5 / params.open_speed.max(0.001);
let t = (state.phase_time / half).min(1.0);
state.left_eye_open = t;
if state.synchronized {
state.right_eye_open = t;
} else {
state.right_eye_open = (t + lcg_next(rng) * 0.05).min(1.0);
}
if state.phase_time >= half {
state.phase = BlinkPhase::Open;
state.phase_time = 0.0;
state.left_eye_open = 1.0;
state.right_eye_open = 1.0;
let base = 1.0 / params.blink_rate_hz.max(0.001);
let var = (lcg_next(rng) * 2.0 - 1.0) * params.variation * base;
state.next_blink_time = (base + var).max(0.1);
}
}
}
}
#[allow(dead_code)]
pub fn trigger_manual_blink(state: &mut BlinkState) {
if state.phase == BlinkPhase::Open {
state.phase = BlinkPhase::Closing;
state.phase_time = 0.0;
}
}
#[allow(dead_code)]
pub fn blink_value(state: &BlinkState) -> f32 {
(state.left_eye_open + state.right_eye_open) * 0.5
}
#[allow(dead_code)]
pub fn set_blink_synchronized(state: &mut BlinkState, sync: bool) {
state.synchronized = sync;
}
#[allow(dead_code)]
pub fn enable_blink(state: &mut BlinkState) {
state.enabled = true;
}
#[allow(dead_code)]
pub fn disable_blink(state: &mut BlinkState) {
state.enabled = false;
}
#[allow(dead_code)]
pub fn is_blinking(state: &BlinkState) -> bool {
state.phase != BlinkPhase::Open
}
#[allow(dead_code)]
pub fn force_open_eyes(state: &mut BlinkState) {
state.phase = BlinkPhase::Open;
state.phase_time = 0.0;
state.left_eye_open = 1.0;
state.right_eye_open = 1.0;
}
#[allow(dead_code)]
pub fn force_close_eyes(state: &mut BlinkState) {
state.phase = BlinkPhase::Closed;
state.phase_time = 0.0;
state.left_eye_open = 0.0;
state.right_eye_open = 0.0;
}
#[allow(dead_code)]
pub fn blink_speed_for_emotion(emotion: &str) -> f32 {
match emotion {
"surprised" => 2.0,
"tired" => 0.5,
_ => 1.0,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_params() {
let p = default_blink_params();
assert!(p.blink_duration > 0.0);
assert!(p.blink_rate_hz > 0.0);
assert!(p.variation >= 0.0);
assert!(p.close_speed > 0.0);
assert!(p.open_speed > 0.0);
}
#[test]
fn test_new_state_defaults() {
let state = new_blink_state(true);
assert_eq!(state.phase, BlinkPhase::Open);
assert!((state.left_eye_open - 1.0).abs() < 1e-6);
assert!((state.right_eye_open - 1.0).abs() < 1e-6);
assert!(state.synchronized);
assert!(state.enabled);
}
#[test]
fn test_update_advances_phase() {
let params = default_blink_params();
let mut state = new_blink_state(true);
let mut rng: u64 = 42;
let trigger_time = state.next_blink_time + 0.01;
update_blink(&mut state, ¶ms, trigger_time, &mut rng);
assert_ne!(state.phase, BlinkPhase::Open);
}
#[test]
fn test_trigger_manual_blink() {
let mut state = new_blink_state(true);
assert_eq!(state.phase, BlinkPhase::Open);
trigger_manual_blink(&mut state);
assert_eq!(state.phase, BlinkPhase::Closing);
}
#[test]
fn test_blink_value_open() {
let state = new_blink_state(true);
assert!((blink_value(&state) - 1.0).abs() < 1e-6);
}
#[test]
fn test_blink_value_closed() {
let mut state = new_blink_state(true);
force_close_eyes(&mut state);
assert!((blink_value(&state)).abs() < 1e-6);
}
#[test]
fn test_enable_disable() {
let mut state = new_blink_state(false);
disable_blink(&mut state);
assert!(!state.enabled);
enable_blink(&mut state);
assert!(state.enabled);
}
#[test]
fn test_is_blinking_open() {
let state = new_blink_state(true);
assert!(!is_blinking(&state));
}
#[test]
fn test_is_blinking_closing() {
let mut state = new_blink_state(true);
trigger_manual_blink(&mut state);
assert!(is_blinking(&state));
}
#[test]
fn test_force_open_eyes() {
let mut state = new_blink_state(true);
force_close_eyes(&mut state);
force_open_eyes(&mut state);
assert_eq!(state.phase, BlinkPhase::Open);
assert!((state.left_eye_open - 1.0).abs() < 1e-6);
}
#[test]
fn test_force_close_eyes() {
let mut state = new_blink_state(true);
force_close_eyes(&mut state);
assert_eq!(state.phase, BlinkPhase::Closed);
assert!((state.left_eye_open).abs() < 1e-6);
}
#[test]
fn test_blink_speed_for_emotion() {
assert!((blink_speed_for_emotion("surprised") - 2.0).abs() < 1e-6);
assert!((blink_speed_for_emotion("tired") - 0.5).abs() < 1e-6);
assert!((blink_speed_for_emotion("neutral") - 1.0).abs() < 1e-6);
}
#[test]
fn test_lcg_next_range() {
let mut rng: u64 = 123456;
for _ in 0..1000 {
let v = lcg_next(&mut rng);
assert!((0.0..1.0).contains(&v), "lcg_next out of [0,1): {}", v);
}
}
#[test]
fn test_set_blink_synchronized() {
let mut state = new_blink_state(true);
set_blink_synchronized(&mut state, false);
assert!(!state.synchronized);
set_blink_synchronized(&mut state, true);
assert!(state.synchronized);
}
#[test]
fn test_full_blink_cycle() {
let params = default_blink_params();
let mut state = new_blink_state(true);
let mut rng: u64 = 999;
trigger_manual_blink(&mut state);
for _ in 0..100 {
update_blink(&mut state, ¶ms, 0.01, &mut rng);
}
assert_eq!(state.phase, BlinkPhase::Open);
}
}