Skip to main content

oxihuman_morph/
face_flatness_control.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan) / SPDX-License-Identifier: Apache-2.0
2#![allow(dead_code)]
3
4//! Face flatness control — antero-posterior depth compression of the face.
5
6/// Config.
7#[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/// State.
22#[allow(dead_code)]
23#[derive(Debug, Clone, Default)]
24pub struct FaceFlatnessState {
25    /// 0 = full depth, 1 = fully flattened.
26    pub flatness: f32,
27    /// Mid-face compression (cheek/zygomatic region), 0..=1.
28    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/// Depth scale factor (1 = no change, 0 = fully flat).
62#[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}