oxihuman_morph/
chin_recession_control.rs1#![allow(dead_code)]
4
5#[allow(dead_code)]
9#[derive(Debug, Clone)]
10pub struct ChinRecessionConfig {
11 pub max_recession: f32,
12 pub max_protrusion: f32,
13}
14
15#[allow(dead_code)]
17#[derive(Debug, Clone)]
18pub struct ChinRecessionState {
19 pub recession: f32,
20 pub vertical_shift: f32,
21 pub lateral_tilt: f32,
22}
23
24#[allow(dead_code)]
25pub fn default_chin_recession_config() -> ChinRecessionConfig {
26 ChinRecessionConfig {
27 max_recession: 1.0,
28 max_protrusion: 1.0,
29 }
30}
31
32#[allow(dead_code)]
33pub fn new_chin_recession_state() -> ChinRecessionState {
34 ChinRecessionState {
35 recession: 0.0,
36 vertical_shift: 0.0,
37 lateral_tilt: 0.0,
38 }
39}
40
41#[allow(dead_code)]
42pub fn chin_rec_set_recession(state: &mut ChinRecessionState, cfg: &ChinRecessionConfig, v: f32) {
43 state.recession = v.clamp(-cfg.max_protrusion, cfg.max_recession);
44}
45
46#[allow(dead_code)]
47pub fn chin_rec_set_vertical(state: &mut ChinRecessionState, v: f32) {
48 state.vertical_shift = v.clamp(-1.0, 1.0);
49}
50
51#[allow(dead_code)]
52pub fn chin_rec_set_tilt(state: &mut ChinRecessionState, v: f32) {
53 state.lateral_tilt = v.clamp(-1.0, 1.0);
54}
55
56#[allow(dead_code)]
57pub fn chin_rec_reset(state: &mut ChinRecessionState) {
58 *state = new_chin_recession_state();
59}
60
61#[allow(dead_code)]
62pub fn chin_rec_is_neutral(state: &ChinRecessionState) -> bool {
63 state.recession.abs() < 1e-6
64 && state.vertical_shift.abs() < 1e-6
65 && state.lateral_tilt.abs() < 1e-6
66}
67
68#[allow(dead_code)]
69pub fn chin_rec_net_offset(state: &ChinRecessionState) -> f32 {
70 state.recession
71}
72
73#[allow(dead_code)]
74pub fn chin_rec_blend(
75 a: &ChinRecessionState,
76 b: &ChinRecessionState,
77 t: f32,
78) -> ChinRecessionState {
79 let t = t.clamp(0.0, 1.0);
80 ChinRecessionState {
81 recession: a.recession + (b.recession - a.recession) * t,
82 vertical_shift: a.vertical_shift + (b.vertical_shift - a.vertical_shift) * t,
83 lateral_tilt: a.lateral_tilt + (b.lateral_tilt - a.lateral_tilt) * t,
84 }
85}
86
87#[allow(dead_code)]
88pub fn chin_rec_to_weights(state: &ChinRecessionState) -> Vec<(String, f32)> {
89 vec![
90 ("chin_recession".to_string(), state.recession),
91 ("chin_vertical_shift".to_string(), state.vertical_shift),
92 ("chin_lateral_tilt".to_string(), state.lateral_tilt),
93 ]
94}
95
96#[allow(dead_code)]
97pub fn chin_rec_to_json(state: &ChinRecessionState) -> String {
98 format!(
99 r#"{{"recession":{:.4},"vertical_shift":{:.4},"lateral_tilt":{:.4}}}"#,
100 state.recession, state.vertical_shift, state.lateral_tilt
101 )
102}
103
104#[cfg(test)]
105mod tests {
106 use super::*;
107
108 #[test]
109 fn default_config() {
110 let cfg = default_chin_recession_config();
111 assert!((cfg.max_recession - 1.0).abs() < 1e-6);
112 }
113
114 #[test]
115 fn new_state_neutral() {
116 let s = new_chin_recession_state();
117 assert!(chin_rec_is_neutral(&s));
118 }
119
120 #[test]
121 fn set_recession_clamps() {
122 let cfg = default_chin_recession_config();
123 let mut s = new_chin_recession_state();
124 chin_rec_set_recession(&mut s, &cfg, 5.0);
125 assert!((s.recession - 1.0).abs() < 1e-6);
126 }
127
128 #[test]
129 fn set_recession_negative() {
130 let cfg = default_chin_recession_config();
131 let mut s = new_chin_recession_state();
132 chin_rec_set_recession(&mut s, &cfg, -0.5);
133 assert!((s.recession + 0.5).abs() < 1e-6);
134 }
135
136 #[test]
137 fn set_vertical() {
138 let mut s = new_chin_recession_state();
139 chin_rec_set_vertical(&mut s, 0.3);
140 assert!((s.vertical_shift - 0.3).abs() < 1e-6);
141 }
142
143 #[test]
144 fn set_tilt_clamps() {
145 let mut s = new_chin_recession_state();
146 chin_rec_set_tilt(&mut s, 2.0);
147 assert!((s.lateral_tilt - 1.0).abs() < 1e-6);
148 }
149
150 #[test]
151 fn reset_clears() {
152 let cfg = default_chin_recession_config();
153 let mut s = new_chin_recession_state();
154 chin_rec_set_recession(&mut s, &cfg, 0.8);
155 chin_rec_reset(&mut s);
156 assert!(chin_rec_is_neutral(&s));
157 }
158
159 #[test]
160 fn blend_midpoint() {
161 let a = new_chin_recession_state();
162 let cfg = default_chin_recession_config();
163 let mut b = new_chin_recession_state();
164 chin_rec_set_recession(&mut b, &cfg, 1.0);
165 let m = chin_rec_blend(&a, &b, 0.5);
166 assert!((m.recession - 0.5).abs() < 1e-6);
167 }
168
169 #[test]
170 fn to_weights_count() {
171 let s = new_chin_recession_state();
172 assert_eq!(chin_rec_to_weights(&s).len(), 3);
173 }
174
175 #[test]
176 fn to_json_fields() {
177 let s = new_chin_recession_state();
178 let j = chin_rec_to_json(&s);
179 assert!(j.contains("recession"));
180 }
181}