oxihuman_morph/
face_flatness_control.rs1#![allow(dead_code)]
3
4#[allow(dead_code)]
8#[derive(Debug, Clone, PartialEq)]
9pub struct FaceFlatnessConfig {
10 pub max_flatten_m: f32,
11}
12
13impl Default for FaceFlatnessConfig {
14 fn default() -> Self {
15 Self {
16 max_flatten_m: 0.018,
17 }
18 }
19}
20
21#[allow(dead_code)]
23#[derive(Debug, Clone, Default)]
24pub struct FaceFlatnessState {
25 pub flatness: f32,
27 pub mid_compress: f32,
29}
30
31#[allow(dead_code)]
32pub fn new_face_flatness_state() -> FaceFlatnessState {
33 FaceFlatnessState::default()
34}
35
36#[allow(dead_code)]
37pub fn default_face_flatness_config() -> FaceFlatnessConfig {
38 FaceFlatnessConfig::default()
39}
40
41#[allow(dead_code)]
42pub fn ffl_set_flatness(state: &mut FaceFlatnessState, v: f32) {
43 state.flatness = v.clamp(0.0, 1.0);
44}
45
46#[allow(dead_code)]
47pub fn ffl_set_mid(state: &mut FaceFlatnessState, v: f32) {
48 state.mid_compress = v.clamp(0.0, 1.0);
49}
50
51#[allow(dead_code)]
52pub fn ffl_reset(state: &mut FaceFlatnessState) {
53 *state = FaceFlatnessState::default();
54}
55
56#[allow(dead_code)]
57pub fn ffl_is_neutral(state: &FaceFlatnessState) -> bool {
58 state.flatness < 1e-4 && state.mid_compress < 1e-4
59}
60
61#[allow(dead_code)]
63pub fn ffl_depth_scale(state: &FaceFlatnessState) -> f32 {
64 1.0 - state.flatness * 0.6
65}
66
67#[allow(dead_code)]
68pub fn ffl_to_weights(state: &FaceFlatnessState, cfg: &FaceFlatnessConfig) -> [f32; 2] {
69 [
70 state.flatness * cfg.max_flatten_m,
71 state.mid_compress * cfg.max_flatten_m * 0.6,
72 ]
73}
74
75#[allow(dead_code)]
76pub fn ffl_blend(a: &FaceFlatnessState, b: &FaceFlatnessState, t: f32) -> FaceFlatnessState {
77 let t = t.clamp(0.0, 1.0);
78 let inv = 1.0 - t;
79 FaceFlatnessState {
80 flatness: a.flatness * inv + b.flatness * t,
81 mid_compress: a.mid_compress * inv + b.mid_compress * t,
82 }
83}
84
85#[allow(dead_code)]
86pub fn ffl_to_json(state: &FaceFlatnessState) -> String {
87 format!(
88 "{{\"flatness\":{:.4},\"mid_compress\":{:.4}}}",
89 state.flatness, state.mid_compress
90 )
91}
92
93#[cfg(test)]
94mod tests {
95 use super::*;
96
97 #[test]
98 fn default_is_neutral() {
99 assert!(ffl_is_neutral(&new_face_flatness_state()));
100 }
101
102 #[test]
103 fn flatness_clamps() {
104 let mut s = new_face_flatness_state();
105 ffl_set_flatness(&mut s, 5.0);
106 assert!((s.flatness - 1.0).abs() < 1e-6);
107 }
108
109 #[test]
110 fn flatness_clamps_low() {
111 let mut s = new_face_flatness_state();
112 ffl_set_flatness(&mut s, -1.0);
113 assert!(s.flatness < 1e-6);
114 }
115
116 #[test]
117 fn reset_works() {
118 let mut s = new_face_flatness_state();
119 ffl_set_flatness(&mut s, 0.9);
120 ffl_reset(&mut s);
121 assert!(ffl_is_neutral(&s));
122 }
123
124 #[test]
125 fn depth_scale_one_at_neutral() {
126 let s = new_face_flatness_state();
127 assert!((ffl_depth_scale(&s) - 1.0).abs() < 1e-6);
128 }
129
130 #[test]
131 fn depth_scale_less_at_max() {
132 let mut s = new_face_flatness_state();
133 ffl_set_flatness(&mut s, 1.0);
134 assert!(ffl_depth_scale(&s) < 1.0);
135 }
136
137 #[test]
138 fn weights_positive() {
139 let cfg = default_face_flatness_config();
140 let mut s = new_face_flatness_state();
141 ffl_set_flatness(&mut s, 1.0);
142 let w = ffl_to_weights(&s, &cfg);
143 assert!(w[0] > 0.0);
144 }
145
146 #[test]
147 fn blend_midpoint() {
148 let b = FaceFlatnessState {
149 flatness: 1.0,
150 mid_compress: 0.0,
151 };
152 let r = ffl_blend(&new_face_flatness_state(), &b, 0.5);
153 assert!((r.flatness - 0.5).abs() < 1e-5);
154 }
155
156 #[test]
157 fn json_keys_present() {
158 let j = ffl_to_json(&new_face_flatness_state());
159 assert!(j.contains("flatness") && j.contains("mid_compress"));
160 }
161
162 #[test]
163 fn mid_clamps() {
164 let mut s = new_face_flatness_state();
165 ffl_set_mid(&mut s, -5.0);
166 assert!(s.mid_compress < 1e-6);
167 }
168}