oxihuman_morph/
cheek_rise_control.rs1#![allow(dead_code)]
4
5#[allow(dead_code)]
9#[derive(Debug, Clone)]
10pub struct CheekRiseConfig {
11 pub max_rise: f32,
12}
13
14#[allow(dead_code)]
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum CheekRiseSide {
18 Left,
19 Right,
20}
21
22#[allow(dead_code)]
24#[derive(Debug, Clone)]
25pub struct CheekRiseState {
26 pub left_rise: f32,
27 pub right_rise: f32,
28}
29
30#[allow(dead_code)]
31pub fn default_cheek_rise_config() -> CheekRiseConfig {
32 CheekRiseConfig { max_rise: 1.0 }
33}
34
35#[allow(dead_code)]
36pub fn new_cheek_rise_state() -> CheekRiseState {
37 CheekRiseState {
38 left_rise: 0.0,
39 right_rise: 0.0,
40 }
41}
42
43#[allow(dead_code)]
44pub fn cr_set_rise(state: &mut CheekRiseState, cfg: &CheekRiseConfig, side: CheekRiseSide, v: f32) {
45 let clamped = v.clamp(0.0, cfg.max_rise);
46 match side {
47 CheekRiseSide::Left => state.left_rise = clamped,
48 CheekRiseSide::Right => state.right_rise = clamped,
49 }
50}
51
52#[allow(dead_code)]
53pub fn cr_set_both(state: &mut CheekRiseState, cfg: &CheekRiseConfig, v: f32) {
54 let clamped = v.clamp(0.0, cfg.max_rise);
55 state.left_rise = clamped;
56 state.right_rise = clamped;
57}
58
59#[allow(dead_code)]
60pub fn cr_reset(state: &mut CheekRiseState) {
61 *state = new_cheek_rise_state();
62}
63
64#[allow(dead_code)]
65pub fn cr_is_neutral(state: &CheekRiseState) -> bool {
66 state.left_rise.abs() < 1e-6 && state.right_rise.abs() < 1e-6
67}
68
69#[allow(dead_code)]
70pub fn cr_average(state: &CheekRiseState) -> f32 {
71 (state.left_rise + state.right_rise) * 0.5
72}
73
74#[allow(dead_code)]
75pub fn cr_symmetry(state: &CheekRiseState) -> f32 {
76 (state.left_rise - state.right_rise).abs()
77}
78
79#[allow(dead_code)]
80pub fn cr_blend(a: &CheekRiseState, b: &CheekRiseState, t: f32) -> CheekRiseState {
81 let t = t.clamp(0.0, 1.0);
82 CheekRiseState {
83 left_rise: a.left_rise + (b.left_rise - a.left_rise) * t,
84 right_rise: a.right_rise + (b.right_rise - a.right_rise) * t,
85 }
86}
87
88#[allow(dead_code)]
89pub fn cr_to_weights(state: &CheekRiseState) -> Vec<(String, f32)> {
90 vec![
91 ("cheek_rise_l".to_string(), state.left_rise),
92 ("cheek_rise_r".to_string(), state.right_rise),
93 ]
94}
95
96#[allow(dead_code)]
97pub fn cr_to_json(state: &CheekRiseState) -> String {
98 format!(
99 r#"{{"left_rise":{:.4},"right_rise":{:.4}}}"#,
100 state.left_rise, state.right_rise
101 )
102}
103
104#[cfg(test)]
105mod tests {
106 use super::*;
107
108 #[test]
109 fn default_config() {
110 let cfg = default_cheek_rise_config();
111 assert!((cfg.max_rise - 1.0).abs() < 1e-6);
112 }
113
114 #[test]
115 fn new_state_neutral() {
116 let s = new_cheek_rise_state();
117 assert!(cr_is_neutral(&s));
118 }
119
120 #[test]
121 fn set_rise_left() {
122 let cfg = default_cheek_rise_config();
123 let mut s = new_cheek_rise_state();
124 cr_set_rise(&mut s, &cfg, CheekRiseSide::Left, 0.7);
125 assert!((s.left_rise - 0.7).abs() < 1e-6);
126 }
127
128 #[test]
129 fn set_rise_clamps() {
130 let cfg = default_cheek_rise_config();
131 let mut s = new_cheek_rise_state();
132 cr_set_rise(&mut s, &cfg, CheekRiseSide::Right, 2.0);
133 assert!((s.right_rise - 1.0).abs() < 1e-6);
134 }
135
136 #[test]
137 fn set_both_symmetric() {
138 let cfg = default_cheek_rise_config();
139 let mut s = new_cheek_rise_state();
140 cr_set_both(&mut s, &cfg, 0.5);
141 assert!(cr_symmetry(&s) < 1e-6);
142 }
143
144 #[test]
145 fn average_value() {
146 let cfg = default_cheek_rise_config();
147 let mut s = new_cheek_rise_state();
148 cr_set_rise(&mut s, &cfg, CheekRiseSide::Left, 0.2);
149 cr_set_rise(&mut s, &cfg, CheekRiseSide::Right, 0.8);
150 assert!((cr_average(&s) - 0.5).abs() < 1e-6);
151 }
152
153 #[test]
154 fn reset_clears() {
155 let cfg = default_cheek_rise_config();
156 let mut s = new_cheek_rise_state();
157 cr_set_both(&mut s, &cfg, 0.9);
158 cr_reset(&mut s);
159 assert!(cr_is_neutral(&s));
160 }
161
162 #[test]
163 fn blend_midpoint() {
164 let a = new_cheek_rise_state();
165 let cfg = default_cheek_rise_config();
166 let mut b = new_cheek_rise_state();
167 cr_set_both(&mut b, &cfg, 1.0);
168 let m = cr_blend(&a, &b, 0.5);
169 assert!((m.left_rise - 0.5).abs() < 1e-6);
170 }
171
172 #[test]
173 fn to_weights_count() {
174 let s = new_cheek_rise_state();
175 assert_eq!(cr_to_weights(&s).len(), 2);
176 }
177
178 #[test]
179 fn to_json_contains_fields() {
180 let s = new_cheek_rise_state();
181 let j = cr_to_json(&s);
182 assert!(j.contains("left_rise"));
183 }
184}