oxihuman_morph/
jaw_shift_control.rs1#![allow(dead_code)]
4
5#[allow(dead_code)]
9#[derive(Debug, Clone)]
10pub struct JawShiftConfig {
11 pub max_lateral: f32,
12 pub max_ap: f32,
13}
14
15#[allow(dead_code)]
17#[derive(Debug, Clone)]
18pub struct JawShiftState {
19 pub lateral: f32,
20 pub anterior: f32,
21 pub posterior: f32,
22 pub torsion: f32,
23}
24
25#[allow(dead_code)]
26pub fn default_jaw_shift_config() -> JawShiftConfig {
27 JawShiftConfig {
28 max_lateral: 1.0,
29 max_ap: 1.0,
30 }
31}
32
33#[allow(dead_code)]
34pub fn new_jaw_shift_state() -> JawShiftState {
35 JawShiftState {
36 lateral: 0.0,
37 anterior: 0.0,
38 posterior: 0.0,
39 torsion: 0.0,
40 }
41}
42
43#[allow(dead_code)]
44pub fn js_set_lateral(state: &mut JawShiftState, cfg: &JawShiftConfig, v: f32) {
45 state.lateral = v.clamp(-cfg.max_lateral, cfg.max_lateral);
46}
47
48#[allow(dead_code)]
49pub fn js_set_anterior(state: &mut JawShiftState, cfg: &JawShiftConfig, v: f32) {
50 state.anterior = v.clamp(0.0, cfg.max_ap);
51 state.posterior = 0.0;
52}
53
54#[allow(dead_code)]
55pub fn js_set_posterior(state: &mut JawShiftState, cfg: &JawShiftConfig, v: f32) {
56 state.posterior = v.clamp(0.0, cfg.max_ap);
57 state.anterior = 0.0;
58}
59
60#[allow(dead_code)]
61pub fn js_set_torsion(state: &mut JawShiftState, v: f32) {
62 state.torsion = v.clamp(-1.0, 1.0);
63}
64
65#[allow(dead_code)]
66pub fn js_reset(state: &mut JawShiftState) {
67 *state = new_jaw_shift_state();
68}
69
70#[allow(dead_code)]
71pub fn js_is_neutral(state: &JawShiftState) -> bool {
72 state.lateral.abs() < 1e-6
73 && state.anterior.abs() < 1e-6
74 && state.posterior.abs() < 1e-6
75 && state.torsion.abs() < 1e-6
76}
77
78#[allow(dead_code)]
79pub fn js_net_ap(state: &JawShiftState) -> f32 {
80 state.anterior - state.posterior
81}
82
83#[allow(dead_code)]
84pub fn js_displacement_magnitude(state: &JawShiftState) -> f32 {
85 let ap = js_net_ap(state);
86 (state.lateral * state.lateral + ap * ap).sqrt()
87}
88
89#[allow(dead_code)]
90pub fn js_blend(a: &JawShiftState, b: &JawShiftState, t: f32) -> JawShiftState {
91 let t = t.clamp(0.0, 1.0);
92 JawShiftState {
93 lateral: a.lateral + (b.lateral - a.lateral) * t,
94 anterior: a.anterior + (b.anterior - a.anterior) * t,
95 posterior: a.posterior + (b.posterior - a.posterior) * t,
96 torsion: a.torsion + (b.torsion - a.torsion) * t,
97 }
98}
99
100#[allow(dead_code)]
101pub fn js_to_weights(state: &JawShiftState) -> Vec<(String, f32)> {
102 vec![
103 ("jaw_lateral_shift".to_string(), state.lateral),
104 ("jaw_anterior".to_string(), state.anterior),
105 ("jaw_posterior".to_string(), state.posterior),
106 ("jaw_torsion".to_string(), state.torsion),
107 ]
108}
109
110#[allow(dead_code)]
111pub fn js_to_json(state: &JawShiftState) -> String {
112 format!(
113 r#"{{"lateral":{:.4},"anterior":{:.4},"posterior":{:.4},"torsion":{:.4}}}"#,
114 state.lateral, state.anterior, state.posterior, state.torsion
115 )
116}
117
118#[cfg(test)]
119mod tests {
120 use super::*;
121
122 #[test]
123 fn default_config() {
124 let cfg = default_jaw_shift_config();
125 assert!((cfg.max_lateral - 1.0).abs() < 1e-6);
126 }
127
128 #[test]
129 fn new_state_neutral() {
130 let s = new_jaw_shift_state();
131 assert!(js_is_neutral(&s));
132 }
133
134 #[test]
135 fn set_lateral_signed() {
136 let cfg = default_jaw_shift_config();
137 let mut s = new_jaw_shift_state();
138 js_set_lateral(&mut s, &cfg, -0.5);
139 assert!((s.lateral + 0.5).abs() < 1e-6);
140 }
141
142 #[test]
143 fn set_anterior_clears_posterior() {
144 let cfg = default_jaw_shift_config();
145 let mut s = new_jaw_shift_state();
146 js_set_posterior(&mut s, &cfg, 0.5);
147 js_set_anterior(&mut s, &cfg, 0.3);
148 assert_eq!(s.posterior, 0.0);
149 assert!((s.anterior - 0.3).abs() < 1e-6);
150 }
151
152 #[test]
153 fn set_posterior_clears_anterior() {
154 let cfg = default_jaw_shift_config();
155 let mut s = new_jaw_shift_state();
156 js_set_anterior(&mut s, &cfg, 0.5);
157 js_set_posterior(&mut s, &cfg, 0.4);
158 assert_eq!(s.anterior, 0.0);
159 assert!((s.posterior - 0.4).abs() < 1e-6);
160 }
161
162 #[test]
163 fn net_ap_anterior() {
164 let cfg = default_jaw_shift_config();
165 let mut s = new_jaw_shift_state();
166 js_set_anterior(&mut s, &cfg, 0.7);
167 assert!((js_net_ap(&s) - 0.7).abs() < 1e-6);
168 }
169
170 #[test]
171 fn reset_clears() {
172 let cfg = default_jaw_shift_config();
173 let mut s = new_jaw_shift_state();
174 js_set_lateral(&mut s, &cfg, 0.5);
175 js_reset(&mut s);
176 assert!(js_is_neutral(&s));
177 }
178
179 #[test]
180 fn displacement_magnitude_zero_at_neutral() {
181 let s = new_jaw_shift_state();
182 assert!(js_displacement_magnitude(&s) < 1e-6);
183 }
184
185 #[test]
186 fn blend_midpoint() {
187 let a = new_jaw_shift_state();
188 let cfg = default_jaw_shift_config();
189 let mut b = new_jaw_shift_state();
190 js_set_lateral(&mut b, &cfg, 1.0);
191 let mid = js_blend(&a, &b, 0.5);
192 assert!((mid.lateral - 0.5).abs() < 1e-6);
193 }
194
195 #[test]
196 fn to_weights_count() {
197 let s = new_jaw_shift_state();
198 assert_eq!(js_to_weights(&s).len(), 4);
199 }
200}