1#[allow(dead_code)]
9#[derive(Clone, Copy, Debug, PartialEq, Eq)]
10pub enum CheekSide {
11 Left,
12 Right,
13 Both,
14}
15
16#[allow(dead_code)]
18#[derive(Clone, Debug)]
19pub struct CheekConfig {
20 pub max_puff: f32,
22 pub max_hollow: f32,
24 pub max_raise: f32,
26 pub smoothing: f32,
28 pub smile_puff_factor: f32,
30}
31
32#[allow(dead_code)]
34#[derive(Clone, Debug)]
35pub struct CheekState {
36 pub puff_left: f32,
38 pub puff_right: f32,
40 pub hollow_left: f32,
42 pub hollow_right: f32,
44 pub raise_left: f32,
46 pub raise_right: f32,
48 pub target_puff_left: f32,
50 pub target_puff_right: f32,
52}
53
54#[allow(dead_code)]
56pub type CheekMorphWeights = Vec<(String, f32)>;
57
58#[allow(dead_code)]
64pub fn default_cheek_config() -> CheekConfig {
65 CheekConfig {
66 max_puff: 1.0,
67 max_hollow: -1.0,
68 max_raise: 1.0,
69 smoothing: 8.0,
70 smile_puff_factor: 0.3,
71 }
72}
73
74#[allow(dead_code)]
76pub fn new_cheek_state() -> CheekState {
77 CheekState {
78 puff_left: 0.0,
79 puff_right: 0.0,
80 hollow_left: 0.0,
81 hollow_right: 0.0,
82 raise_left: 0.0,
83 raise_right: 0.0,
84 target_puff_left: 0.0,
85 target_puff_right: 0.0,
86 }
87}
88
89fn clamp01(v: f32) -> f32 {
94 v.clamp(0.0, 1.0)
95}
96
97fn clamp_neg1_0(v: f32) -> f32 {
98 v.clamp(-1.0, 0.0)
99}
100
101#[allow(dead_code)]
103pub fn set_cheek_puff(state: &mut CheekState, side: CheekSide, amount: f32) {
104 let v = clamp01(amount);
105 match side {
106 CheekSide::Left => {
107 state.target_puff_left = v;
108 }
109 CheekSide::Right => {
110 state.target_puff_right = v;
111 }
112 CheekSide::Both => {
113 state.target_puff_left = v;
114 state.target_puff_right = v;
115 }
116 }
117}
118
119#[allow(dead_code)]
121pub fn set_cheek_hollow(state: &mut CheekState, side: CheekSide, amount: f32) {
122 let v = clamp_neg1_0(amount);
123 match side {
124 CheekSide::Left => state.hollow_left = v,
125 CheekSide::Right => state.hollow_right = v,
126 CheekSide::Both => {
127 state.hollow_left = v;
128 state.hollow_right = v;
129 }
130 }
131}
132
133#[allow(dead_code)]
135pub fn set_cheek_raise(state: &mut CheekState, side: CheekSide, amount: f32) {
136 let v = clamp01(amount);
137 match side {
138 CheekSide::Left => state.raise_left = v,
139 CheekSide::Right => state.raise_right = v,
140 CheekSide::Both => {
141 state.raise_left = v;
142 state.raise_right = v;
143 }
144 }
145}
146
147#[allow(dead_code)]
154pub fn update_cheeks(state: &mut CheekState, cfg: &CheekConfig, dt: f32) {
155 let t = (cfg.smoothing * dt).min(1.0);
156 state.puff_left += (state.target_puff_left - state.puff_left) * t;
157 state.puff_right += (state.target_puff_right - state.puff_right) * t;
158}
159
160#[allow(dead_code)]
166pub fn cheek_puff_left(state: &CheekState) -> f32 {
167 state.puff_left
168}
169
170#[allow(dead_code)]
172pub fn cheek_puff_right(state: &CheekState) -> f32 {
173 state.puff_right
174}
175
176#[allow(dead_code)]
178pub fn cheek_hollow_left(state: &CheekState) -> f32 {
179 state.hollow_left
180}
181
182#[allow(dead_code)]
184pub fn cheek_hollow_right(state: &CheekState) -> f32 {
185 state.hollow_right
186}
187
188#[allow(dead_code)]
194pub fn blend_cheek_states(a: &CheekState, b: &CheekState, t: f32) -> CheekState {
195 let t = clamp01(t);
196 let lerp = |x: f32, y: f32| x + (y - x) * t;
197 CheekState {
198 puff_left: lerp(a.puff_left, b.puff_left),
199 puff_right: lerp(a.puff_right, b.puff_right),
200 hollow_left: lerp(a.hollow_left, b.hollow_left),
201 hollow_right: lerp(a.hollow_right, b.hollow_right),
202 raise_left: lerp(a.raise_left, b.raise_left),
203 raise_right: lerp(a.raise_right, b.raise_right),
204 target_puff_left: lerp(a.target_puff_left, b.target_puff_left),
205 target_puff_right: lerp(a.target_puff_right, b.target_puff_right),
206 }
207}
208
209#[allow(dead_code)]
211pub fn cheek_to_morph_weights(state: &CheekState) -> CheekMorphWeights {
212 vec![
213 ("cheek_puff_left".to_string(), state.puff_left),
214 ("cheek_puff_right".to_string(), state.puff_right),
215 ("cheek_hollow_left".to_string(), -state.hollow_left),
216 ("cheek_hollow_right".to_string(), -state.hollow_right),
217 ("cheek_raise_left".to_string(), state.raise_left),
218 ("cheek_raise_right".to_string(), state.raise_right),
219 ]
220}
221
222#[allow(dead_code)]
228pub fn reset_cheeks(state: &mut CheekState) {
229 *state = new_cheek_state();
230}
231
232#[allow(dead_code)]
238pub fn apply_smile_effect(state: &mut CheekState, cfg: &CheekConfig, smile: f32) {
239 let smile = clamp01(smile);
240 let puff = smile * cfg.smile_puff_factor;
241 let raise = smile * cfg.smile_puff_factor;
242 set_cheek_puff(state, CheekSide::Both, puff);
243 set_cheek_raise(state, CheekSide::Both, raise);
244 state.puff_left = state.target_puff_left;
246 state.puff_right = state.target_puff_right;
247}
248
249#[cfg(test)]
254mod tests {
255 use super::*;
256
257 #[test]
258 fn test_default_cheek_config_values() {
259 let cfg = default_cheek_config();
260 assert_eq!(cfg.max_puff, 1.0);
261 assert_eq!(cfg.max_hollow, -1.0);
262 assert!(cfg.smoothing > 0.0);
263 }
264
265 #[test]
266 fn test_new_cheek_state_zeroed() {
267 let s = new_cheek_state();
268 assert_eq!(s.puff_left, 0.0);
269 assert_eq!(s.puff_right, 0.0);
270 assert_eq!(s.hollow_left, 0.0);
271 assert_eq!(s.hollow_right, 0.0);
272 }
273
274 #[test]
275 fn test_set_cheek_puff_left() {
276 let mut s = new_cheek_state();
277 set_cheek_puff(&mut s, CheekSide::Left, 0.7);
278 assert!((s.target_puff_left - 0.7).abs() < 1e-5);
279 assert_eq!(s.target_puff_right, 0.0);
280 }
281
282 #[test]
283 fn test_set_cheek_puff_right() {
284 let mut s = new_cheek_state();
285 set_cheek_puff(&mut s, CheekSide::Right, 0.5);
286 assert!((s.target_puff_right - 0.5).abs() < 1e-5);
287 assert_eq!(s.target_puff_left, 0.0);
288 }
289
290 #[test]
291 fn test_set_cheek_puff_both() {
292 let mut s = new_cheek_state();
293 set_cheek_puff(&mut s, CheekSide::Both, 0.9);
294 assert!((s.target_puff_left - 0.9).abs() < 1e-5);
295 assert!((s.target_puff_right - 0.9).abs() < 1e-5);
296 }
297
298 #[test]
299 fn test_set_cheek_puff_clamped_above() {
300 let mut s = new_cheek_state();
301 set_cheek_puff(&mut s, CheekSide::Both, 2.0);
302 assert!((s.target_puff_left - 1.0).abs() < 1e-5);
303 }
304
305 #[test]
306 fn test_set_cheek_hollow_clamped() {
307 let mut s = new_cheek_state();
308 set_cheek_hollow(&mut s, CheekSide::Both, -0.5);
309 assert!((s.hollow_left - (-0.5)).abs() < 1e-5);
310 set_cheek_hollow(&mut s, CheekSide::Both, -2.0);
311 assert!((s.hollow_left - (-1.0)).abs() < 1e-5);
312 }
313
314 #[test]
315 fn test_set_cheek_raise() {
316 let mut s = new_cheek_state();
317 set_cheek_raise(&mut s, CheekSide::Left, 0.4);
318 assert!((s.raise_left - 0.4).abs() < 1e-5);
319 assert_eq!(s.raise_right, 0.0);
320 }
321
322 #[test]
323 fn test_update_cheeks_smoothing() {
324 let cfg = default_cheek_config();
325 let mut s = new_cheek_state();
326 set_cheek_puff(&mut s, CheekSide::Left, 1.0);
327 update_cheeks(&mut s, &cfg, 1.0);
328 assert!(s.puff_left > 0.0);
329 assert!(s.puff_left <= 1.0);
330 }
331
332 #[test]
333 fn test_cheek_puff_accessors() {
334 let mut s = new_cheek_state();
335 s.puff_left = 0.3;
336 s.puff_right = 0.6;
337 assert!((cheek_puff_left(&s) - 0.3).abs() < 1e-5);
338 assert!((cheek_puff_right(&s) - 0.6).abs() < 1e-5);
339 }
340
341 #[test]
342 fn test_cheek_hollow_accessors() {
343 let mut s = new_cheek_state();
344 s.hollow_left = -0.4;
345 s.hollow_right = -0.8;
346 assert!((cheek_hollow_left(&s) - (-0.4)).abs() < 1e-5);
347 assert!((cheek_hollow_right(&s) - (-0.8)).abs() < 1e-5);
348 }
349
350 #[test]
351 fn test_blend_cheek_states_midpoint() {
352 let mut a = new_cheek_state();
353 let mut b = new_cheek_state();
354 a.puff_left = 0.0;
355 b.puff_left = 1.0;
356 let blended = blend_cheek_states(&a, &b, 0.5);
357 assert!((blended.puff_left - 0.5).abs() < 1e-5);
358 }
359
360 #[test]
361 fn test_blend_cheek_states_at_zero() {
362 let a = new_cheek_state();
363 let b = new_cheek_state();
364 let blended = blend_cheek_states(&a, &b, 0.0);
365 assert_eq!(blended.puff_left, a.puff_left);
366 }
367
368 #[test]
369 fn test_cheek_to_morph_weights_keys() {
370 let s = new_cheek_state();
371 let weights = cheek_to_morph_weights(&s);
372 assert_eq!(weights.len(), 6);
373 let keys: Vec<&str> = weights.iter().map(|(k, _)| k.as_str()).collect();
374 assert!(keys.contains(&"cheek_puff_left"));
375 assert!(keys.contains(&"cheek_hollow_left"));
376 assert!(keys.contains(&"cheek_raise_right"));
377 }
378
379 #[test]
380 fn test_reset_cheeks() {
381 let mut s = new_cheek_state();
382 s.puff_left = 0.9;
383 s.hollow_right = -0.5;
384 reset_cheeks(&mut s);
385 assert_eq!(s.puff_left, 0.0);
386 assert_eq!(s.hollow_right, 0.0);
387 }
388
389 #[test]
390 fn test_apply_smile_effect_puffs_cheeks() {
391 let cfg = default_cheek_config();
392 let mut s = new_cheek_state();
393 apply_smile_effect(&mut s, &cfg, 1.0);
394 assert!(s.puff_left > 0.0);
395 assert!(s.puff_right > 0.0);
396 }
397
398 #[test]
399 fn test_apply_smile_effect_zero_smile() {
400 let cfg = default_cheek_config();
401 let mut s = new_cheek_state();
402 apply_smile_effect(&mut s, &cfg, 0.0);
403 assert_eq!(s.puff_left, 0.0);
404 assert_eq!(s.puff_right, 0.0);
405 }
406}