oxihuman_morph/
face_width_v2_control.rs1#![allow(dead_code)]
4
5use std::f32::consts::FRAC_1_SQRT_2;
8
9#[allow(dead_code)]
11#[derive(Debug, Clone)]
12pub struct FaceWidthV2Config {
13 pub diagonal_factor: f32,
15 pub link_regions: bool,
17}
18
19impl Default for FaceWidthV2Config {
20 fn default() -> Self {
21 FaceWidthV2Config {
22 diagonal_factor: FRAC_1_SQRT_2,
23 link_regions: false,
24 }
25 }
26}
27
28#[allow(dead_code)]
30#[derive(Debug, Clone)]
31pub struct FaceWidthV2State {
32 bizygomatic: f32,
34 bigonial: f32,
36 temporal: f32,
38 config: FaceWidthV2Config,
39}
40
41pub fn default_face_width_v2_config() -> FaceWidthV2Config {
43 FaceWidthV2Config::default()
44}
45
46pub fn new_face_width_v2_state(config: FaceWidthV2Config) -> FaceWidthV2State {
48 FaceWidthV2State {
49 bizygomatic: 0.5,
50 bigonial: 0.5,
51 temporal: 0.5,
52 config,
53 }
54}
55
56pub fn fw2_set_bizygomatic(state: &mut FaceWidthV2State, v: f32) {
58 state.bizygomatic = v.clamp(0.0, 1.0);
59 if state.config.link_regions {
60 state.bigonial = state.bizygomatic;
61 }
62}
63
64pub fn fw2_set_bigonial(state: &mut FaceWidthV2State, v: f32) {
66 state.bigonial = v.clamp(0.0, 1.0);
67}
68
69pub fn fw2_set_temporal(state: &mut FaceWidthV2State, v: f32) {
71 state.temporal = v.clamp(0.0, 1.0);
72}
73
74pub fn fw2_reset(state: &mut FaceWidthV2State) {
76 state.bizygomatic = 0.5;
77 state.bigonial = 0.5;
78 state.temporal = 0.5;
79}
80
81pub fn fw2_is_neutral(state: &FaceWidthV2State) -> bool {
83 (state.bizygomatic - 0.5).abs() < 1e-5
84 && (state.bigonial - 0.5).abs() < 1e-5
85 && (state.temporal - 0.5).abs() < 1e-5
86}
87
88pub fn fw2_average_width(state: &FaceWidthV2State) -> f32 {
90 (state.bizygomatic * 0.5 + state.bigonial * 0.3 + state.temporal * 0.2).clamp(0.0, 1.0)
91}
92
93pub fn fw2_to_weights(state: &FaceWidthV2State) -> [f32; 3] {
95 [state.bizygomatic, state.bigonial, state.temporal]
96}
97
98pub fn fw2_blend(a: &FaceWidthV2State, b: &FaceWidthV2State, t: f32) -> FaceWidthV2State {
100 let t = t.clamp(0.0, 1.0);
101 FaceWidthV2State {
102 bizygomatic: a.bizygomatic + (b.bizygomatic - a.bizygomatic) * t,
103 bigonial: a.bigonial + (b.bigonial - a.bigonial) * t,
104 temporal: a.temporal + (b.temporal - a.temporal) * t,
105 config: a.config.clone(),
106 }
107}
108
109pub fn fw2_to_json(state: &FaceWidthV2State) -> String {
111 format!(
112 r#"{{"bizygomatic":{:.4},"bigonial":{:.4},"temporal":{:.4}}}"#,
113 state.bizygomatic, state.bigonial, state.temporal
114 )
115}
116
117#[cfg(test)]
121mod tests {
122 use super::*;
123
124 fn make() -> FaceWidthV2State {
125 new_face_width_v2_state(default_face_width_v2_config())
126 }
127
128 #[test]
129 fn neutral_on_creation() {
130 assert!(fw2_is_neutral(&make()));
131 }
132
133 #[test]
134 fn set_bizygomatic_clamps() {
135 let mut s = make();
136 fw2_set_bizygomatic(&mut s, 5.0);
137 assert!((s.bizygomatic - 1.0).abs() < 1e-5);
138 }
139
140 #[test]
141 fn reset_restores_neutral() {
142 let mut s = make();
143 fw2_set_bizygomatic(&mut s, 0.1);
144 fw2_reset(&mut s);
145 assert!(fw2_is_neutral(&s));
146 }
147
148 #[test]
149 fn average_in_range() {
150 let s = make();
151 assert!((0.0..=1.0).contains(&fw2_average_width(&s)));
152 }
153
154 #[test]
155 fn weights_in_range() {
156 let s = make();
157 for v in fw2_to_weights(&s) {
158 assert!((0.0..=1.0).contains(&v));
159 }
160 }
161
162 #[test]
163 fn blend_at_one_is_b() {
164 let mut b = make();
165 fw2_set_bizygomatic(&mut b, 0.9);
166 let r = fw2_blend(&make(), &b, 1.0);
167 assert!((r.bizygomatic - 0.9).abs() < 1e-5);
168 }
169
170 #[test]
171 fn blend_midpoint() {
172 let mut a = make();
173 let mut b = make();
174 fw2_set_bizygomatic(&mut a, 0.0);
175 fw2_set_bizygomatic(&mut b, 1.0);
176 let m = fw2_blend(&a, &b, 0.5);
177 assert!((m.bizygomatic - 0.5).abs() < 1e-5);
178 }
179
180 #[test]
181 fn json_has_bizygomatic() {
182 assert!(fw2_to_json(&make()).contains("bizygomatic"));
183 }
184
185 #[test]
186 fn link_regions_propagates() {
187 let mut cfg = default_face_width_v2_config();
188 cfg.link_regions = true;
189 let mut s = new_face_width_v2_state(cfg);
190 fw2_set_bizygomatic(&mut s, 0.3);
191 assert!((s.bigonial - 0.3).abs() < 1e-5);
192 }
193}