Skip to main content

oxihuman_morph/
face_vertical_control.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! Face vertical control — vertical proportions of the face regions.
6
7/// Configuration for face vertical control.
8#[allow(dead_code)]
9#[derive(Debug, Clone)]
10pub struct FaceVerticalConfig {
11    pub max_scale: f32,
12}
13
14/// Runtime state.
15#[allow(dead_code)]
16#[derive(Debug, Clone)]
17pub struct FaceVerticalState {
18    pub upper_third: f32,
19    pub middle_third: f32,
20    pub lower_third: f32,
21}
22
23#[allow(dead_code)]
24pub fn default_face_vertical_config() -> FaceVerticalConfig {
25    FaceVerticalConfig { max_scale: 1.5 }
26}
27
28#[allow(dead_code)]
29pub fn new_face_vertical_state() -> FaceVerticalState {
30    FaceVerticalState {
31        upper_third: 0.0,
32        middle_third: 0.0,
33        lower_third: 0.0,
34    }
35}
36
37#[allow(dead_code)]
38pub fn fv_set_upper(state: &mut FaceVerticalState, cfg: &FaceVerticalConfig, v: f32) {
39    state.upper_third = v.clamp(-cfg.max_scale, cfg.max_scale);
40}
41
42#[allow(dead_code)]
43pub fn fv_set_middle(state: &mut FaceVerticalState, cfg: &FaceVerticalConfig, v: f32) {
44    state.middle_third = v.clamp(-cfg.max_scale, cfg.max_scale);
45}
46
47#[allow(dead_code)]
48pub fn fv_set_lower(state: &mut FaceVerticalState, cfg: &FaceVerticalConfig, v: f32) {
49    state.lower_third = v.clamp(-cfg.max_scale, cfg.max_scale);
50}
51
52#[allow(dead_code)]
53pub fn fv_set_all(state: &mut FaceVerticalState, cfg: &FaceVerticalConfig, v: f32) {
54    let clamped = v.clamp(-cfg.max_scale, cfg.max_scale);
55    state.upper_third = clamped;
56    state.middle_third = clamped;
57    state.lower_third = clamped;
58}
59
60#[allow(dead_code)]
61pub fn fv_reset(state: &mut FaceVerticalState) {
62    *state = new_face_vertical_state();
63}
64
65#[allow(dead_code)]
66pub fn fv_is_neutral(state: &FaceVerticalState) -> bool {
67    state.upper_third.abs() < 1e-6
68        && state.middle_third.abs() < 1e-6
69        && state.lower_third.abs() < 1e-6
70}
71
72#[allow(dead_code)]
73pub fn fv_total_scale(state: &FaceVerticalState) -> f32 {
74    state.upper_third + state.middle_third + state.lower_third
75}
76
77#[allow(dead_code)]
78pub fn fv_blend(a: &FaceVerticalState, b: &FaceVerticalState, t: f32) -> FaceVerticalState {
79    let t = t.clamp(0.0, 1.0);
80    FaceVerticalState {
81        upper_third: a.upper_third + (b.upper_third - a.upper_third) * t,
82        middle_third: a.middle_third + (b.middle_third - a.middle_third) * t,
83        lower_third: a.lower_third + (b.lower_third - a.lower_third) * t,
84    }
85}
86
87#[allow(dead_code)]
88pub fn fv_to_weights(state: &FaceVerticalState) -> Vec<(String, f32)> {
89    vec![
90        ("face_upper_third".to_string(), state.upper_third),
91        ("face_middle_third".to_string(), state.middle_third),
92        ("face_lower_third".to_string(), state.lower_third),
93    ]
94}
95
96#[allow(dead_code)]
97pub fn fv_to_json(state: &FaceVerticalState) -> String {
98    format!(
99        r#"{{"upper_third":{:.4},"middle_third":{:.4},"lower_third":{:.4}}}"#,
100        state.upper_third, state.middle_third, state.lower_third
101    )
102}
103
104#[cfg(test)]
105mod tests {
106    use super::*;
107
108    #[test]
109    fn default_config() {
110        let cfg = default_face_vertical_config();
111        assert!((cfg.max_scale - 1.5).abs() < 1e-6);
112    }
113
114    #[test]
115    fn new_state_neutral() {
116        let s = new_face_vertical_state();
117        assert!(fv_is_neutral(&s));
118    }
119
120    #[test]
121    fn set_upper_clamps() {
122        let cfg = default_face_vertical_config();
123        let mut s = new_face_vertical_state();
124        fv_set_upper(&mut s, &cfg, 5.0);
125        assert!((s.upper_third - 1.5).abs() < 1e-6);
126    }
127
128    #[test]
129    fn set_middle() {
130        let cfg = default_face_vertical_config();
131        let mut s = new_face_vertical_state();
132        fv_set_middle(&mut s, &cfg, 0.3);
133        assert!((s.middle_third - 0.3).abs() < 1e-6);
134    }
135
136    #[test]
137    fn set_all() {
138        let cfg = default_face_vertical_config();
139        let mut s = new_face_vertical_state();
140        fv_set_all(&mut s, &cfg, 0.5);
141        assert!((fv_total_scale(&s) - 1.5).abs() < 1e-6);
142    }
143
144    #[test]
145    fn reset_clears() {
146        let cfg = default_face_vertical_config();
147        let mut s = new_face_vertical_state();
148        fv_set_lower(&mut s, &cfg, 0.9);
149        fv_reset(&mut s);
150        assert!(fv_is_neutral(&s));
151    }
152
153    #[test]
154    fn blend_midpoint() {
155        let a = new_face_vertical_state();
156        let cfg = default_face_vertical_config();
157        let mut b = new_face_vertical_state();
158        fv_set_upper(&mut b, &cfg, 1.0);
159        let m = fv_blend(&a, &b, 0.5);
160        assert!((m.upper_third - 0.5).abs() < 1e-6);
161    }
162
163    #[test]
164    fn to_weights_count() {
165        let s = new_face_vertical_state();
166        assert_eq!(fv_to_weights(&s).len(), 3);
167    }
168
169    #[test]
170    fn total_scale_sum() {
171        let cfg = default_face_vertical_config();
172        let mut s = new_face_vertical_state();
173        fv_set_upper(&mut s, &cfg, 0.1);
174        fv_set_middle(&mut s, &cfg, 0.2);
175        fv_set_lower(&mut s, &cfg, 0.3);
176        assert!((fv_total_scale(&s) - 0.6).abs() < 1e-6);
177    }
178
179    #[test]
180    fn to_json_fields() {
181        let s = new_face_vertical_state();
182        let j = fv_to_json(&s);
183        assert!(j.contains("upper_third"));
184    }
185}