Skip to main content

oxihuman_morph/
brow_wrinkle_control.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! Brow wrinkle morph — controls horizontal and vertical furrow lines on the brow.
6
7/// Configuration for brow wrinkle control.
8#[allow(dead_code)]
9#[derive(Debug, Clone)]
10pub struct BrowWrinkleConfig {
11    pub max_depth: f32,
12}
13
14/// Brow wrinkle runtime state.
15#[allow(dead_code)]
16#[derive(Debug, Clone)]
17pub struct BrowWrinkleState {
18    pub horizontal_depth: f32,
19    pub vertical_furrow: f32,
20    pub left_arch_wrinkle: f32,
21    pub right_arch_wrinkle: f32,
22}
23
24#[allow(dead_code)]
25pub fn default_brow_wrinkle_config() -> BrowWrinkleConfig {
26    BrowWrinkleConfig { max_depth: 1.0 }
27}
28
29#[allow(dead_code)]
30pub fn new_brow_wrinkle_state() -> BrowWrinkleState {
31    BrowWrinkleState {
32        horizontal_depth: 0.0,
33        vertical_furrow: 0.0,
34        left_arch_wrinkle: 0.0,
35        right_arch_wrinkle: 0.0,
36    }
37}
38
39#[allow(dead_code)]
40pub fn bw_set_horizontal(state: &mut BrowWrinkleState, cfg: &BrowWrinkleConfig, v: f32) {
41    state.horizontal_depth = v.clamp(0.0, cfg.max_depth);
42}
43
44#[allow(dead_code)]
45pub fn bw_set_vertical(state: &mut BrowWrinkleState, cfg: &BrowWrinkleConfig, v: f32) {
46    state.vertical_furrow = v.clamp(0.0, cfg.max_depth);
47}
48
49#[allow(dead_code)]
50pub fn bw_set_arch(state: &mut BrowWrinkleState, cfg: &BrowWrinkleConfig, left: f32, right: f32) {
51    state.left_arch_wrinkle = left.clamp(0.0, cfg.max_depth);
52    state.right_arch_wrinkle = right.clamp(0.0, cfg.max_depth);
53}
54
55#[allow(dead_code)]
56pub fn bw_reset(state: &mut BrowWrinkleState) {
57    *state = new_brow_wrinkle_state();
58}
59
60#[allow(dead_code)]
61pub fn bw_is_neutral(state: &BrowWrinkleState) -> bool {
62    let vals = [
63        state.horizontal_depth,
64        state.vertical_furrow,
65        state.left_arch_wrinkle,
66        state.right_arch_wrinkle,
67    ];
68    !vals.is_empty() && vals.iter().all(|v| v.abs() < 1e-6)
69}
70
71#[allow(dead_code)]
72pub fn bw_intensity(state: &BrowWrinkleState) -> f32 {
73    let vals = [
74        state.horizontal_depth,
75        state.vertical_furrow,
76        state.left_arch_wrinkle,
77        state.right_arch_wrinkle,
78    ];
79    vals.iter().cloned().fold(0.0_f32, f32::max)
80}
81
82#[allow(dead_code)]
83pub fn bw_blend(a: &BrowWrinkleState, b: &BrowWrinkleState, t: f32) -> BrowWrinkleState {
84    let t = t.clamp(0.0, 1.0);
85    BrowWrinkleState {
86        horizontal_depth: a.horizontal_depth + (b.horizontal_depth - a.horizontal_depth) * t,
87        vertical_furrow: a.vertical_furrow + (b.vertical_furrow - a.vertical_furrow) * t,
88        left_arch_wrinkle: a.left_arch_wrinkle + (b.left_arch_wrinkle - a.left_arch_wrinkle) * t,
89        right_arch_wrinkle: a.right_arch_wrinkle
90            + (b.right_arch_wrinkle - a.right_arch_wrinkle) * t,
91    }
92}
93
94#[allow(dead_code)]
95pub fn bw_symmetry(state: &BrowWrinkleState) -> f32 {
96    (state.left_arch_wrinkle - state.right_arch_wrinkle).abs()
97}
98
99#[allow(dead_code)]
100pub fn bw_to_weights(state: &BrowWrinkleState) -> Vec<(String, f32)> {
101    vec![
102        (
103            "brow_horizontal_wrinkle".to_string(),
104            state.horizontal_depth,
105        ),
106        ("brow_vertical_furrow".to_string(), state.vertical_furrow),
107        ("brow_arch_wrinkle_l".to_string(), state.left_arch_wrinkle),
108        ("brow_arch_wrinkle_r".to_string(), state.right_arch_wrinkle),
109    ]
110}
111
112#[allow(dead_code)]
113pub fn bw_to_json(state: &BrowWrinkleState) -> String {
114    format!(
115        r#"{{"horizontal_depth":{:.4},"vertical_furrow":{:.4},"left_arch":{:.4},"right_arch":{:.4}}}"#,
116        state.horizontal_depth,
117        state.vertical_furrow,
118        state.left_arch_wrinkle,
119        state.right_arch_wrinkle
120    )
121}
122
123#[cfg(test)]
124mod tests {
125    use super::*;
126
127    #[test]
128    fn default_config_max() {
129        let cfg = default_brow_wrinkle_config();
130        assert!((cfg.max_depth - 1.0).abs() < 1e-6);
131    }
132
133    #[test]
134    fn new_state_neutral() {
135        let s = new_brow_wrinkle_state();
136        assert!(bw_is_neutral(&s));
137    }
138
139    #[test]
140    fn set_horizontal_clamps() {
141        let cfg = default_brow_wrinkle_config();
142        let mut s = new_brow_wrinkle_state();
143        bw_set_horizontal(&mut s, &cfg, 5.0);
144        assert!((s.horizontal_depth - 1.0).abs() < 1e-6);
145    }
146
147    #[test]
148    fn set_vertical_negative_clamped() {
149        let cfg = default_brow_wrinkle_config();
150        let mut s = new_brow_wrinkle_state();
151        bw_set_vertical(&mut s, &cfg, -1.0);
152        assert_eq!(s.vertical_furrow, 0.0);
153    }
154
155    #[test]
156    fn set_arch_both_sides() {
157        let cfg = default_brow_wrinkle_config();
158        let mut s = new_brow_wrinkle_state();
159        bw_set_arch(&mut s, &cfg, 0.3, 0.7);
160        assert!((s.left_arch_wrinkle - 0.3).abs() < 1e-6);
161        assert!((s.right_arch_wrinkle - 0.7).abs() < 1e-6);
162    }
163
164    #[test]
165    fn reset_clears() {
166        let cfg = default_brow_wrinkle_config();
167        let mut s = new_brow_wrinkle_state();
168        bw_set_horizontal(&mut s, &cfg, 0.5);
169        bw_reset(&mut s);
170        assert!(bw_is_neutral(&s));
171    }
172
173    #[test]
174    fn intensity_max() {
175        let cfg = default_brow_wrinkle_config();
176        let mut s = new_brow_wrinkle_state();
177        bw_set_horizontal(&mut s, &cfg, 0.6);
178        bw_set_vertical(&mut s, &cfg, 0.9);
179        let i = bw_intensity(&s);
180        assert!((i - 0.9).abs() < 1e-6);
181    }
182
183    #[test]
184    fn blend_midpoint() {
185        let a = new_brow_wrinkle_state();
186        let cfg = default_brow_wrinkle_config();
187        let mut b = new_brow_wrinkle_state();
188        bw_set_horizontal(&mut b, &cfg, 1.0);
189        let mid = bw_blend(&a, &b, 0.5);
190        assert!((mid.horizontal_depth - 0.5).abs() < 1e-6);
191    }
192
193    #[test]
194    fn symmetry_zero_when_equal() {
195        let cfg = default_brow_wrinkle_config();
196        let mut s = new_brow_wrinkle_state();
197        bw_set_arch(&mut s, &cfg, 0.4, 0.4);
198        assert!(bw_symmetry(&s) < 1e-6);
199    }
200
201    #[test]
202    fn to_weights_count() {
203        let s = new_brow_wrinkle_state();
204        assert_eq!(bw_to_weights(&s).len(), 4);
205    }
206
207    #[test]
208    fn to_json_contains_keys() {
209        let s = new_brow_wrinkle_state();
210        let j = bw_to_json(&s);
211        assert!(j.contains("horizontal_depth"));
212        assert!(j.contains("right_arch"));
213    }
214}