oxihuman_morph/
eye_droop_control.rs1#![allow(dead_code)]
3
4use std::f32::consts::FRAC_PI_8;
7
8#[allow(dead_code)]
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum EyeSide {
12 Left,
13 Right,
14}
15
16#[allow(dead_code)]
18#[derive(Debug, Clone, PartialEq)]
19pub struct EyeDroopConfig {
20 pub max_droop_m: f32,
21}
22
23impl Default for EyeDroopConfig {
24 fn default() -> Self {
25 Self { max_droop_m: 0.006 }
26 }
27}
28
29#[allow(dead_code)]
31#[derive(Debug, Clone, Default)]
32pub struct EyeDroopState {
33 pub left: f32,
34 pub right: f32,
35}
36
37#[allow(dead_code)]
38pub fn new_eye_droop_state() -> EyeDroopState {
39 EyeDroopState::default()
40}
41
42#[allow(dead_code)]
43pub fn default_eye_droop_config() -> EyeDroopConfig {
44 EyeDroopConfig::default()
45}
46
47#[allow(dead_code)]
48pub fn edr_set(state: &mut EyeDroopState, side: EyeSide, v: f32) {
49 let v = v.clamp(0.0, 1.0);
50 match side {
51 EyeSide::Left => state.left = v,
52 EyeSide::Right => state.right = v,
53 }
54}
55
56#[allow(dead_code)]
57pub fn edr_set_both(state: &mut EyeDroopState, v: f32) {
58 let v = v.clamp(0.0, 1.0);
59 state.left = v;
60 state.right = v;
61}
62
63#[allow(dead_code)]
64pub fn edr_reset(state: &mut EyeDroopState) {
65 *state = EyeDroopState::default();
66}
67
68#[allow(dead_code)]
69pub fn edr_is_neutral(state: &EyeDroopState) -> bool {
70 state.left < 1e-4 && state.right < 1e-4
71}
72
73#[allow(dead_code)]
74pub fn edr_asymmetry(state: &EyeDroopState) -> f32 {
75 (state.left - state.right).abs()
76}
77
78#[allow(dead_code)]
80pub fn edr_lid_angle_rad(state: &EyeDroopState, side: EyeSide) -> f32 {
81 let v = match side {
82 EyeSide::Left => state.left,
83 EyeSide::Right => state.right,
84 };
85 v * FRAC_PI_8
86}
87
88#[allow(dead_code)]
89pub fn edr_to_weights(state: &EyeDroopState, cfg: &EyeDroopConfig) -> [f32; 2] {
90 [state.left * cfg.max_droop_m, state.right * cfg.max_droop_m]
91}
92
93#[allow(dead_code)]
94pub fn edr_blend(a: &EyeDroopState, b: &EyeDroopState, t: f32) -> EyeDroopState {
95 let t = t.clamp(0.0, 1.0);
96 let inv = 1.0 - t;
97 EyeDroopState {
98 left: a.left * inv + b.left * t,
99 right: a.right * inv + b.right * t,
100 }
101}
102
103#[allow(dead_code)]
104pub fn edr_to_json(state: &EyeDroopState) -> String {
105 format!(
106 "{{\"left\":{:.4},\"right\":{:.4}}}",
107 state.left, state.right
108 )
109}
110
111#[cfg(test)]
112mod tests {
113 use super::*;
114
115 #[test]
116 fn default_neutral() {
117 assert!(edr_is_neutral(&new_eye_droop_state()));
118 }
119
120 #[test]
121 fn set_clamps_high() {
122 let mut s = new_eye_droop_state();
123 edr_set(&mut s, EyeSide::Left, 10.0);
124 assert!((s.left - 1.0).abs() < 1e-6);
125 }
126
127 #[test]
128 fn set_clamps_low() {
129 let mut s = new_eye_droop_state();
130 edr_set(&mut s, EyeSide::Right, -1.0);
131 assert!(s.right < 1e-6);
132 }
133
134 #[test]
135 fn reset_works() {
136 let mut s = new_eye_droop_state();
137 edr_set_both(&mut s, 0.5);
138 edr_reset(&mut s);
139 assert!(edr_is_neutral(&s));
140 }
141
142 #[test]
143 fn asymmetry_zero_when_equal() {
144 let mut s = new_eye_droop_state();
145 edr_set_both(&mut s, 0.5);
146 assert!(edr_asymmetry(&s) < 1e-6);
147 }
148
149 #[test]
150 fn lid_angle_positive() {
151 let mut s = new_eye_droop_state();
152 edr_set(&mut s, EyeSide::Left, 1.0);
153 assert!(edr_lid_angle_rad(&s, EyeSide::Left) > 0.0);
154 }
155
156 #[test]
157 fn weights_correct() {
158 let cfg = default_eye_droop_config();
159 let mut s = new_eye_droop_state();
160 edr_set_both(&mut s, 1.0);
161 let w = edr_to_weights(&s, &cfg);
162 assert!((w[0] - cfg.max_droop_m).abs() < 1e-6);
163 }
164
165 #[test]
166 fn blend_midpoint() {
167 let mut b = new_eye_droop_state();
168 edr_set_both(&mut b, 1.0);
169 let r = edr_blend(&new_eye_droop_state(), &b, 0.5);
170 assert!((r.left - 0.5).abs() < 1e-5);
171 }
172
173 #[test]
174 fn json_has_left_right() {
175 let j = edr_to_json(&new_eye_droop_state());
176 assert!(j.contains("left") && j.contains("right"));
177 }
178
179 #[test]
180 fn set_both_equal() {
181 let mut s = new_eye_droop_state();
182 edr_set_both(&mut s, 0.7);
183 assert!((s.left - s.right).abs() < 1e-6);
184 }
185}