1#[allow(dead_code)]
9#[derive(Clone, Copy, Debug, PartialEq, Eq)]
10pub enum BrowSide {
11 Left,
12 Right,
13 Both,
14}
15
16#[allow(dead_code)]
18#[derive(Clone, Debug)]
19pub struct BrowConfig {
20 pub max_inner_raise: f32,
22 pub max_outer_raise: f32,
24 pub max_lower: f32,
26 pub max_furrow: f32,
28 pub max_arch: f32,
30 pub smoothing: f32,
32}
33
34#[allow(dead_code)]
36#[derive(Clone, Debug)]
37pub struct BrowState {
38 pub inner_raise_left: f32,
40 pub inner_raise_right: f32,
42 pub outer_raise_left: f32,
44 pub outer_raise_right: f32,
46 pub lower_left: f32,
48 pub lower_right: f32,
50 pub furrow: f32,
52 pub arch_left: f32,
54 pub arch_right: f32,
56 pub target_inner_left: f32,
58 pub target_inner_right: f32,
60}
61
62#[allow(dead_code)]
64pub type BrowMorphWeights = Vec<(String, f32)>;
65
66#[allow(dead_code)]
72pub fn default_brow_config() -> BrowConfig {
73 BrowConfig {
74 max_inner_raise: 1.0,
75 max_outer_raise: 1.0,
76 max_lower: 1.0,
77 max_furrow: 1.0,
78 max_arch: 1.0,
79 smoothing: 10.0,
80 }
81}
82
83#[allow(dead_code)]
85pub fn new_brow_state() -> BrowState {
86 BrowState {
87 inner_raise_left: 0.0,
88 inner_raise_right: 0.0,
89 outer_raise_left: 0.0,
90 outer_raise_right: 0.0,
91 lower_left: 0.0,
92 lower_right: 0.0,
93 furrow: 0.0,
94 arch_left: 0.0,
95 arch_right: 0.0,
96 target_inner_left: 0.0,
97 target_inner_right: 0.0,
98 }
99}
100
101fn clamp01(v: f32) -> f32 {
106 v.clamp(0.0, 1.0)
107}
108
109#[allow(dead_code)]
112pub fn set_brow_raise(state: &mut BrowState, side: BrowSide, amount: f32, inner: bool) {
113 let v = clamp01(amount);
114 match (side, inner) {
115 (BrowSide::Left, true) => state.target_inner_left = v,
116 (BrowSide::Right, true) => state.target_inner_right = v,
117 (BrowSide::Both, true) => {
118 state.target_inner_left = v;
119 state.target_inner_right = v;
120 }
121 (BrowSide::Left, false) => state.outer_raise_left = v,
122 (BrowSide::Right, false) => state.outer_raise_right = v,
123 (BrowSide::Both, false) => {
124 state.outer_raise_left = v;
125 state.outer_raise_right = v;
126 }
127 }
128}
129
130#[allow(dead_code)]
132pub fn set_brow_lower(state: &mut BrowState, side: BrowSide, amount: f32) {
133 let v = clamp01(amount);
134 match side {
135 BrowSide::Left => state.lower_left = v,
136 BrowSide::Right => state.lower_right = v,
137 BrowSide::Both => {
138 state.lower_left = v;
139 state.lower_right = v;
140 }
141 }
142}
143
144#[allow(dead_code)]
146pub fn set_brow_furrow(state: &mut BrowState, amount: f32) {
147 state.furrow = clamp01(amount);
148}
149
150#[allow(dead_code)]
152pub fn set_brow_arch(state: &mut BrowState, side: BrowSide, amount: f32) {
153 let v = clamp01(amount);
154 match side {
155 BrowSide::Left => state.arch_left = v,
156 BrowSide::Right => state.arch_right = v,
157 BrowSide::Both => {
158 state.arch_left = v;
159 state.arch_right = v;
160 }
161 }
162}
163
164#[allow(dead_code)]
171pub fn update_brows(state: &mut BrowState, cfg: &BrowConfig, dt: f32) {
172 let t = (cfg.smoothing * dt).min(1.0);
173 state.inner_raise_left += (state.target_inner_left - state.inner_raise_left) * t;
174 state.inner_raise_right += (state.target_inner_right - state.inner_raise_right) * t;
175}
176
177#[allow(dead_code)]
183pub fn brow_raise_left(state: &BrowState) -> f32 {
184 (state.inner_raise_left + state.outer_raise_left) * 0.5
185}
186
187#[allow(dead_code)]
189pub fn brow_raise_right(state: &BrowState) -> f32 {
190 (state.inner_raise_right + state.outer_raise_right) * 0.5
191}
192
193#[allow(dead_code)]
195pub fn brow_furrow_amount(state: &BrowState) -> f32 {
196 state.furrow
197}
198
199#[allow(dead_code)]
205pub fn blend_brow_states(a: &BrowState, b: &BrowState, t: f32) -> BrowState {
206 let t = clamp01(t);
207 let lerp = |x: f32, y: f32| x + (y - x) * t;
208 BrowState {
209 inner_raise_left: lerp(a.inner_raise_left, b.inner_raise_left),
210 inner_raise_right: lerp(a.inner_raise_right, b.inner_raise_right),
211 outer_raise_left: lerp(a.outer_raise_left, b.outer_raise_left),
212 outer_raise_right: lerp(a.outer_raise_right, b.outer_raise_right),
213 lower_left: lerp(a.lower_left, b.lower_left),
214 lower_right: lerp(a.lower_right, b.lower_right),
215 furrow: lerp(a.furrow, b.furrow),
216 arch_left: lerp(a.arch_left, b.arch_left),
217 arch_right: lerp(a.arch_right, b.arch_right),
218 target_inner_left: lerp(a.target_inner_left, b.target_inner_left),
219 target_inner_right: lerp(a.target_inner_right, b.target_inner_right),
220 }
221}
222
223#[allow(dead_code)]
225pub fn brow_to_morph_weights(state: &BrowState) -> BrowMorphWeights {
226 vec![
227 ("brow_inner_raise_left".to_string(), state.inner_raise_left),
228 (
229 "brow_inner_raise_right".to_string(),
230 state.inner_raise_right,
231 ),
232 ("brow_outer_raise_left".to_string(), state.outer_raise_left),
233 (
234 "brow_outer_raise_right".to_string(),
235 state.outer_raise_right,
236 ),
237 ("brow_lower_left".to_string(), state.lower_left),
238 ("brow_lower_right".to_string(), state.lower_right),
239 ("brow_furrow".to_string(), state.furrow),
240 ("brow_arch_left".to_string(), state.arch_left),
241 ("brow_arch_right".to_string(), state.arch_right),
242 ]
243}
244
245#[allow(dead_code)]
251pub fn reset_brows(state: &mut BrowState) {
252 *state = new_brow_state();
253}
254
255#[allow(dead_code)]
262pub fn emotion_to_brow(emotion: &str, cfg: &BrowConfig) -> BrowState {
263 let mut s = new_brow_state();
264 match emotion {
265 "angry" => {
266 set_brow_lower(&mut s, BrowSide::Both, 0.6 * cfg.max_lower);
267 set_brow_furrow(&mut s, 0.8 * cfg.max_furrow);
268 }
269 "sad" => {
270 set_brow_raise(&mut s, BrowSide::Both, 0.5 * cfg.max_inner_raise, true);
271 set_brow_lower(&mut s, BrowSide::Both, 0.2 * cfg.max_lower);
272 }
273 "surprised" => {
274 set_brow_raise(&mut s, BrowSide::Both, 0.9 * cfg.max_inner_raise, true);
275 set_brow_raise(&mut s, BrowSide::Both, 0.9 * cfg.max_outer_raise, false);
276 s.inner_raise_left = s.target_inner_left;
278 s.inner_raise_right = s.target_inner_right;
279 }
280 "happy" => {
281 set_brow_raise(&mut s, BrowSide::Both, 0.2 * cfg.max_outer_raise, false);
282 s.arch_left = 0.3 * cfg.max_arch;
283 s.arch_right = 0.3 * cfg.max_arch;
284 }
285 "fearful" => {
286 set_brow_raise(&mut s, BrowSide::Both, 0.7 * cfg.max_inner_raise, true);
287 set_brow_furrow(&mut s, 0.4 * cfg.max_furrow);
288 s.inner_raise_left = s.target_inner_left;
289 s.inner_raise_right = s.target_inner_right;
290 }
291 _ => {} }
293 s
294}
295
296#[cfg(test)]
301mod tests {
302 use super::*;
303
304 #[test]
305 fn test_default_brow_config_non_zero() {
306 let cfg = default_brow_config();
307 assert!(cfg.max_inner_raise > 0.0);
308 assert!(cfg.max_furrow > 0.0);
309 assert!(cfg.smoothing > 0.0);
310 }
311
312 #[test]
313 fn test_new_brow_state_zeroed() {
314 let s = new_brow_state();
315 assert_eq!(s.inner_raise_left, 0.0);
316 assert_eq!(s.furrow, 0.0);
317 assert_eq!(s.arch_right, 0.0);
318 }
319
320 #[test]
321 fn test_set_brow_raise_inner_left() {
322 let mut s = new_brow_state();
323 set_brow_raise(&mut s, BrowSide::Left, 0.8, true);
324 assert!((s.target_inner_left - 0.8).abs() < 1e-5);
325 assert_eq!(s.target_inner_right, 0.0);
326 }
327
328 #[test]
329 fn test_set_brow_raise_outer_both() {
330 let mut s = new_brow_state();
331 set_brow_raise(&mut s, BrowSide::Both, 0.6, false);
332 assert!((s.outer_raise_left - 0.6).abs() < 1e-5);
333 assert!((s.outer_raise_right - 0.6).abs() < 1e-5);
334 }
335
336 #[test]
337 fn test_set_brow_raise_clamped() {
338 let mut s = new_brow_state();
339 set_brow_raise(&mut s, BrowSide::Both, 2.0, true);
340 assert!((s.target_inner_left - 1.0).abs() < 1e-5);
341 }
342
343 #[test]
344 fn test_set_brow_lower() {
345 let mut s = new_brow_state();
346 set_brow_lower(&mut s, BrowSide::Right, 0.5);
347 assert!((s.lower_right - 0.5).abs() < 1e-5);
348 assert_eq!(s.lower_left, 0.0);
349 }
350
351 #[test]
352 fn test_set_brow_furrow() {
353 let mut s = new_brow_state();
354 set_brow_furrow(&mut s, 0.7);
355 assert!((s.furrow - 0.7).abs() < 1e-5);
356 }
357
358 #[test]
359 fn test_set_brow_arch() {
360 let mut s = new_brow_state();
361 set_brow_arch(&mut s, BrowSide::Left, 0.4);
362 assert!((s.arch_left - 0.4).abs() < 1e-5);
363 assert_eq!(s.arch_right, 0.0);
364 }
365
366 #[test]
367 fn test_update_brows_smoothing() {
368 let cfg = default_brow_config();
369 let mut s = new_brow_state();
370 set_brow_raise(&mut s, BrowSide::Left, 1.0, true);
371 update_brows(&mut s, &cfg, 1.0);
372 assert!(s.inner_raise_left > 0.0);
373 }
374
375 #[test]
376 fn test_brow_raise_left_accessor() {
377 let mut s = new_brow_state();
378 s.inner_raise_left = 0.4;
379 s.outer_raise_left = 0.6;
380 let avg = brow_raise_left(&s);
381 assert!((avg - 0.5).abs() < 1e-5);
382 }
383
384 #[test]
385 fn test_brow_furrow_amount_accessor() {
386 let mut s = new_brow_state();
387 s.furrow = 0.3;
388 assert!((brow_furrow_amount(&s) - 0.3).abs() < 1e-5);
389 }
390
391 #[test]
392 fn test_blend_brow_states_midpoint() {
393 let mut a = new_brow_state();
394 let mut b = new_brow_state();
395 a.furrow = 0.0;
396 b.furrow = 1.0;
397 let blended = blend_brow_states(&a, &b, 0.5);
398 assert!((blended.furrow - 0.5).abs() < 1e-5);
399 }
400
401 #[test]
402 fn test_brow_to_morph_weights_count() {
403 let s = new_brow_state();
404 let w = brow_to_morph_weights(&s);
405 assert_eq!(w.len(), 9);
406 }
407
408 #[test]
409 fn test_brow_to_morph_weights_keys_present() {
410 let s = new_brow_state();
411 let w = brow_to_morph_weights(&s);
412 let keys: Vec<&str> = w.iter().map(|(k, _)| k.as_str()).collect();
413 assert!(keys.contains(&"brow_furrow"));
414 assert!(keys.contains(&"brow_arch_left"));
415 }
416
417 #[test]
418 fn test_reset_brows() {
419 let mut s = new_brow_state();
420 s.furrow = 0.9;
421 s.arch_left = 0.5;
422 reset_brows(&mut s);
423 assert_eq!(s.furrow, 0.0);
424 assert_eq!(s.arch_left, 0.0);
425 }
426
427 #[test]
428 fn test_emotion_angry_sets_lower_and_furrow() {
429 let cfg = default_brow_config();
430 let s = emotion_to_brow("angry", &cfg);
431 assert!(s.lower_left > 0.0);
432 assert!(s.furrow > 0.0);
433 }
434
435 #[test]
436 fn test_emotion_surprised_raises_brows() {
437 let cfg = default_brow_config();
438 let s = emotion_to_brow("surprised", &cfg);
439 assert!(s.inner_raise_left > 0.0);
440 assert!(s.outer_raise_right > 0.0);
441 }
442
443 #[test]
444 fn test_emotion_neutral_zeroed() {
445 let cfg = default_brow_config();
446 let s = emotion_to_brow("neutral", &cfg);
447 assert_eq!(s.furrow, 0.0);
448 assert_eq!(s.inner_raise_left, 0.0);
449 }
450}