Skip to main content

oxihuman_morph/
brow_height_control.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3
4#![allow(dead_code)]
5
6//! Brow height morph control: adjusts vertical position of the brow ridge.
7
8/// Configuration for brow height morphing.
9#[allow(dead_code)]
10#[derive(Debug, Clone)]
11pub struct BrowHeightConfig {
12    pub min_height: f32,
13    pub max_height: f32,
14}
15
16/// Runtime state for brow height morph.
17#[allow(dead_code)]
18#[derive(Debug, Clone)]
19pub struct BrowHeightState {
20    pub left_height: f32,
21    pub right_height: f32,
22    pub symmetry: f32,
23}
24
25#[allow(dead_code)]
26pub fn default_brow_height_config() -> BrowHeightConfig {
27    BrowHeightConfig {
28        min_height: -1.0,
29        max_height: 1.0,
30    }
31}
32
33#[allow(dead_code)]
34pub fn new_brow_height_state() -> BrowHeightState {
35    BrowHeightState {
36        left_height: 0.0,
37        right_height: 0.0,
38        symmetry: 1.0,
39    }
40}
41
42#[allow(dead_code)]
43pub fn browh_set_left(state: &mut BrowHeightState, cfg: &BrowHeightConfig, v: f32) {
44    state.left_height = v.clamp(cfg.min_height, cfg.max_height);
45}
46
47#[allow(dead_code)]
48pub fn browh_set_right(state: &mut BrowHeightState, cfg: &BrowHeightConfig, v: f32) {
49    state.right_height = v.clamp(cfg.min_height, cfg.max_height);
50}
51
52#[allow(dead_code)]
53pub fn browh_set_both(state: &mut BrowHeightState, cfg: &BrowHeightConfig, v: f32) {
54    let clamped = v.clamp(cfg.min_height, cfg.max_height);
55    state.left_height = clamped;
56    state.right_height = clamped;
57}
58
59#[allow(dead_code)]
60pub fn browh_set_symmetry(state: &mut BrowHeightState, v: f32) {
61    state.symmetry = v.clamp(0.0, 1.0);
62}
63
64#[allow(dead_code)]
65pub fn browh_reset(state: &mut BrowHeightState) {
66    *state = new_brow_height_state();
67}
68
69#[allow(dead_code)]
70pub fn browh_effective_right(state: &BrowHeightState) -> f32 {
71    state.left_height * state.symmetry + state.right_height * (1.0 - state.symmetry)
72}
73
74#[allow(dead_code)]
75pub fn browh_to_weights(state: &BrowHeightState) -> Vec<(String, f32)> {
76    vec![
77        ("brow_height_left".to_string(), state.left_height),
78        (
79            "brow_height_right".to_string(),
80            browh_effective_right(state),
81        ),
82    ]
83}
84
85#[allow(dead_code)]
86pub fn browh_to_json(state: &BrowHeightState) -> String {
87    format!(
88        r#"{{"left_height":{:.4},"right_height":{:.4},"symmetry":{:.4}}}"#,
89        state.left_height, state.right_height, state.symmetry
90    )
91}
92
93#[allow(dead_code)]
94pub fn browh_blend(a: &BrowHeightState, b: &BrowHeightState, t: f32) -> BrowHeightState {
95    let t = t.clamp(0.0, 1.0);
96    BrowHeightState {
97        left_height: a.left_height + (b.left_height - a.left_height) * t,
98        right_height: a.right_height + (b.right_height - a.right_height) * t,
99        symmetry: a.symmetry + (b.symmetry - a.symmetry) * t,
100    }
101}
102
103#[cfg(test)]
104mod tests {
105    use super::*;
106
107    #[test]
108    fn test_default_config() {
109        let cfg = default_brow_height_config();
110        assert!((cfg.min_height + 1.0).abs() < 1e-6);
111        assert!((cfg.max_height - 1.0).abs() < 1e-6);
112    }
113
114    #[test]
115    fn test_new_state() {
116        let s = new_brow_height_state();
117        assert!(s.left_height.abs() < 1e-6);
118        assert!((s.symmetry - 1.0).abs() < 1e-6);
119    }
120
121    #[test]
122    fn test_set_left_clamps() {
123        let cfg = default_brow_height_config();
124        let mut s = new_brow_height_state();
125        browh_set_left(&mut s, &cfg, 5.0);
126        assert!((s.left_height - 1.0).abs() < 1e-6);
127    }
128
129    #[test]
130    fn test_set_both() {
131        let cfg = default_brow_height_config();
132        let mut s = new_brow_height_state();
133        browh_set_both(&mut s, &cfg, 0.5);
134        assert!((s.left_height - 0.5).abs() < 1e-6);
135        assert!((s.right_height - 0.5).abs() < 1e-6);
136    }
137
138    #[test]
139    fn test_symmetry() {
140        let mut s = new_brow_height_state();
141        browh_set_symmetry(&mut s, 0.0);
142        assert!(s.symmetry.abs() < 1e-6);
143    }
144
145    #[test]
146    fn test_effective_right_full_symmetry() {
147        let mut s = new_brow_height_state();
148        s.left_height = 0.8;
149        s.right_height = 0.2;
150        s.symmetry = 1.0;
151        assert!((browh_effective_right(&s) - 0.8).abs() < 1e-6);
152    }
153
154    #[test]
155    fn test_reset() {
156        let cfg = default_brow_height_config();
157        let mut s = new_brow_height_state();
158        browh_set_left(&mut s, &cfg, 0.7);
159        browh_reset(&mut s);
160        assert!(s.left_height.abs() < 1e-6);
161    }
162
163    #[test]
164    fn test_to_weights_count() {
165        let s = new_brow_height_state();
166        assert_eq!(browh_to_weights(&s).len(), 2);
167    }
168
169    #[test]
170    fn test_to_json() {
171        let s = new_brow_height_state();
172        let j = browh_to_json(&s);
173        assert!(j.contains("left_height"));
174    }
175
176    #[test]
177    fn test_blend() {
178        let a = new_brow_height_state();
179        let mut b = new_brow_height_state();
180        b.left_height = 1.0;
181        let mid = browh_blend(&a, &b, 0.5);
182        assert!((mid.left_height - 0.5).abs() < 1e-6);
183    }
184}