oxihuman_morph/
eye_fissure_control.rs1#![allow(dead_code)]
4
5use std::f32::consts::FRAC_PI_6;
8
9#[allow(dead_code)]
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum EyeSide {
12 Left,
13 Right,
14}
15
16#[allow(dead_code)]
17#[derive(Debug, Clone)]
18pub struct EyeFissureConfig {
19 pub max_opening: f32,
20}
21
22impl Default for EyeFissureConfig {
23 fn default() -> Self {
24 Self { max_opening: 1.0 }
25 }
26}
27
28#[allow(dead_code)]
29#[derive(Debug, Clone)]
30pub struct EyeFissureState {
31 pub left: f32,
32 pub right: f32,
33 pub config: EyeFissureConfig,
34}
35
36#[allow(dead_code)]
37pub fn default_eye_fissure_config() -> EyeFissureConfig {
38 EyeFissureConfig::default()
39}
40
41#[allow(dead_code)]
42pub fn new_eye_fissure_state(config: EyeFissureConfig) -> EyeFissureState {
43 EyeFissureState {
44 left: 0.0,
45 right: 0.0,
46 config,
47 }
48}
49
50#[allow(dead_code)]
51pub fn ef_set(state: &mut EyeFissureState, side: EyeSide, v: f32) {
52 let v = v.clamp(0.0, state.config.max_opening);
53 match side {
54 EyeSide::Left => state.left = v,
55 EyeSide::Right => state.right = v,
56 }
57}
58
59#[allow(dead_code)]
60pub fn ef_set_both(state: &mut EyeFissureState, v: f32) {
61 let v = v.clamp(0.0, state.config.max_opening);
62 state.left = v;
63 state.right = v;
64}
65
66#[allow(dead_code)]
67pub fn ef_reset(state: &mut EyeFissureState) {
68 state.left = 0.0;
69 state.right = 0.0;
70}
71
72#[allow(dead_code)]
73pub fn ef_is_neutral(state: &EyeFissureState) -> bool {
74 state.left.abs() < 1e-6 && state.right.abs() < 1e-6
75}
76
77#[allow(dead_code)]
78pub fn ef_average(state: &EyeFissureState) -> f32 {
79 (state.left + state.right) * 0.5
80}
81
82#[allow(dead_code)]
83pub fn ef_asymmetry(state: &EyeFissureState) -> f32 {
84 (state.left - state.right).abs()
85}
86
87#[allow(dead_code)]
88pub fn ef_opening_angle_rad(state: &EyeFissureState) -> f32 {
89 ef_average(state) * FRAC_PI_6
90}
91
92#[allow(dead_code)]
93pub fn ef_to_weights(state: &EyeFissureState) -> [f32; 2] {
94 let m = state.config.max_opening;
95 let n = |v: f32| if m > 1e-9 { v / m } else { 0.0 };
96 [n(state.left), n(state.right)]
97}
98
99#[allow(dead_code)]
100pub fn ef_blend(a: &EyeFissureState, b: &EyeFissureState, t: f32) -> [f32; 2] {
101 let t = t.clamp(0.0, 1.0);
102 [
103 a.left * (1.0 - t) + b.left * t,
104 a.right * (1.0 - t) + b.right * t,
105 ]
106}
107
108#[allow(dead_code)]
109pub fn ef_to_json(state: &EyeFissureState) -> String {
110 format!(
111 "{{\"left\":{:.4},\"right\":{:.4}}}",
112 state.left, state.right
113 )
114}
115
116#[cfg(test)]
117mod tests {
118 use super::*;
119 #[test]
120 fn default_neutral() {
121 assert!(ef_is_neutral(&new_eye_fissure_state(
122 default_eye_fissure_config()
123 )));
124 }
125 #[test]
126 fn set_clamps() {
127 let mut s = new_eye_fissure_state(default_eye_fissure_config());
128 ef_set(&mut s, EyeSide::Left, 10.0);
129 assert!((0.0..=1.0).contains(&s.left));
130 }
131 #[test]
132 fn set_both_mirrors() {
133 let mut s = new_eye_fissure_state(default_eye_fissure_config());
134 ef_set_both(&mut s, 0.5);
135 assert!((s.right - 0.5).abs() < 1e-5);
136 }
137 #[test]
138 fn reset_zeroes() {
139 let mut s = new_eye_fissure_state(default_eye_fissure_config());
140 ef_set_both(&mut s, 0.9);
141 ef_reset(&mut s);
142 assert!(ef_is_neutral(&s));
143 }
144 #[test]
145 fn average_mid() {
146 let mut s = new_eye_fissure_state(default_eye_fissure_config());
147 ef_set(&mut s, EyeSide::Left, 0.2);
148 ef_set(&mut s, EyeSide::Right, 0.8);
149 assert!((ef_average(&s) - 0.5).abs() < 1e-5);
150 }
151 #[test]
152 fn asymmetry_abs() {
153 let mut s = new_eye_fissure_state(default_eye_fissure_config());
154 ef_set(&mut s, EyeSide::Left, 0.3);
155 ef_set(&mut s, EyeSide::Right, 0.7);
156 assert!((ef_asymmetry(&s) - 0.4).abs() < 1e-5);
157 }
158 #[test]
159 fn angle_nonneg() {
160 let s = new_eye_fissure_state(default_eye_fissure_config());
161 assert!(ef_opening_angle_rad(&s) >= 0.0);
162 }
163 #[test]
164 fn to_weights_at_max() {
165 let mut s = new_eye_fissure_state(default_eye_fissure_config());
166 ef_set(&mut s, EyeSide::Left, 1.0);
167 assert!((ef_to_weights(&s)[0] - 1.0).abs() < 1e-5);
168 }
169 #[test]
170 fn blend_at_zero_is_a() {
171 let mut a = new_eye_fissure_state(default_eye_fissure_config());
172 let b = new_eye_fissure_state(default_eye_fissure_config());
173 ef_set(&mut a, EyeSide::Left, 0.7);
174 let w = ef_blend(&a, &b, 0.0);
175 assert!((w[0] - 0.7).abs() < 1e-5);
176 }
177 #[test]
178 fn to_json_has_left() {
179 assert!(
180 ef_to_json(&new_eye_fissure_state(default_eye_fissure_config())).contains("\"left\"")
181 );
182 }
183}