Skip to main content

oxihuman_morph/
forehead_raise_control.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! Forehead raise morph — controls how raised the forehead region is.
6
7/// Configuration for forehead raise control.
8#[allow(dead_code)]
9#[derive(Debug, Clone)]
10pub struct ForeheadRaiseConfig {
11    pub max_raise: f32,
12}
13
14/// Runtime state.
15#[allow(dead_code)]
16#[derive(Debug, Clone)]
17pub struct ForeheadRaiseState {
18    pub center_raise: f32,
19    pub left_raise: f32,
20    pub right_raise: f32,
21    pub skin_tension: f32,
22}
23
24#[allow(dead_code)]
25pub fn default_forehead_raise_config() -> ForeheadRaiseConfig {
26    ForeheadRaiseConfig { max_raise: 1.0 }
27}
28
29#[allow(dead_code)]
30pub fn new_forehead_raise_state() -> ForeheadRaiseState {
31    ForeheadRaiseState {
32        center_raise: 0.0,
33        left_raise: 0.0,
34        right_raise: 0.0,
35        skin_tension: 0.0,
36    }
37}
38
39#[allow(dead_code)]
40pub fn fhr_set_center(state: &mut ForeheadRaiseState, cfg: &ForeheadRaiseConfig, v: f32) {
41    state.center_raise = v.clamp(0.0, cfg.max_raise);
42}
43
44#[allow(dead_code)]
45pub fn fhr_set_sides(
46    state: &mut ForeheadRaiseState,
47    cfg: &ForeheadRaiseConfig,
48    left: f32,
49    right: f32,
50) {
51    state.left_raise = left.clamp(0.0, cfg.max_raise);
52    state.right_raise = right.clamp(0.0, cfg.max_raise);
53}
54
55#[allow(dead_code)]
56pub fn fhr_set_all(state: &mut ForeheadRaiseState, cfg: &ForeheadRaiseConfig, v: f32) {
57    let clamped = v.clamp(0.0, cfg.max_raise);
58    state.center_raise = clamped;
59    state.left_raise = clamped;
60    state.right_raise = clamped;
61}
62
63#[allow(dead_code)]
64pub fn fhr_set_tension(state: &mut ForeheadRaiseState, v: f32) {
65    state.skin_tension = v.clamp(0.0, 1.0);
66}
67
68#[allow(dead_code)]
69pub fn fhr_reset(state: &mut ForeheadRaiseState) {
70    *state = new_forehead_raise_state();
71}
72
73#[allow(dead_code)]
74pub fn fhr_is_neutral(state: &ForeheadRaiseState) -> bool {
75    let vals = [
76        state.center_raise,
77        state.left_raise,
78        state.right_raise,
79        state.skin_tension,
80    ];
81    !vals.is_empty() && vals.iter().all(|v| v.abs() < 1e-6)
82}
83
84#[allow(dead_code)]
85pub fn fhr_average_raise(state: &ForeheadRaiseState) -> f32 {
86    (state.center_raise + state.left_raise + state.right_raise) / 3.0
87}
88
89#[allow(dead_code)]
90pub fn fhr_symmetry(state: &ForeheadRaiseState) -> f32 {
91    (state.left_raise - state.right_raise).abs()
92}
93
94#[allow(dead_code)]
95pub fn fhr_blend(a: &ForeheadRaiseState, b: &ForeheadRaiseState, t: f32) -> ForeheadRaiseState {
96    let t = t.clamp(0.0, 1.0);
97    ForeheadRaiseState {
98        center_raise: a.center_raise + (b.center_raise - a.center_raise) * t,
99        left_raise: a.left_raise + (b.left_raise - a.left_raise) * t,
100        right_raise: a.right_raise + (b.right_raise - a.right_raise) * t,
101        skin_tension: a.skin_tension + (b.skin_tension - a.skin_tension) * t,
102    }
103}
104
105#[allow(dead_code)]
106pub fn fhr_to_weights(state: &ForeheadRaiseState) -> Vec<(String, f32)> {
107    vec![
108        ("forehead_raise_center".to_string(), state.center_raise),
109        ("forehead_raise_left".to_string(), state.left_raise),
110        ("forehead_raise_right".to_string(), state.right_raise),
111        ("forehead_skin_tension".to_string(), state.skin_tension),
112    ]
113}
114
115#[allow(dead_code)]
116pub fn fhr_to_json(state: &ForeheadRaiseState) -> String {
117    format!(
118        r#"{{"center_raise":{:.4},"left_raise":{:.4},"right_raise":{:.4},"skin_tension":{:.4}}}"#,
119        state.center_raise, state.left_raise, state.right_raise, state.skin_tension
120    )
121}
122
123#[cfg(test)]
124mod tests {
125    use super::*;
126
127    #[test]
128    fn default_config() {
129        let cfg = default_forehead_raise_config();
130        assert!((cfg.max_raise - 1.0).abs() < 1e-6);
131    }
132
133    #[test]
134    fn new_state_neutral() {
135        let s = new_forehead_raise_state();
136        assert!(fhr_is_neutral(&s));
137    }
138
139    #[test]
140    fn set_center_clamps() {
141        let cfg = default_forehead_raise_config();
142        let mut s = new_forehead_raise_state();
143        fhr_set_center(&mut s, &cfg, 5.0);
144        assert!((s.center_raise - 1.0).abs() < 1e-6);
145    }
146
147    #[test]
148    fn set_sides() {
149        let cfg = default_forehead_raise_config();
150        let mut s = new_forehead_raise_state();
151        fhr_set_sides(&mut s, &cfg, 0.3, 0.7);
152        assert!((s.left_raise - 0.3).abs() < 1e-6);
153        assert!((s.right_raise - 0.7).abs() < 1e-6);
154    }
155
156    #[test]
157    fn set_all_equal() {
158        let cfg = default_forehead_raise_config();
159        let mut s = new_forehead_raise_state();
160        fhr_set_all(&mut s, &cfg, 0.6);
161        assert!((s.center_raise - 0.6).abs() < 1e-6);
162        assert!((s.left_raise - 0.6).abs() < 1e-6);
163        assert!((s.right_raise - 0.6).abs() < 1e-6);
164    }
165
166    #[test]
167    fn symmetry_zero_when_equal() {
168        let cfg = default_forehead_raise_config();
169        let mut s = new_forehead_raise_state();
170        fhr_set_sides(&mut s, &cfg, 0.5, 0.5);
171        assert!(fhr_symmetry(&s) < 1e-6);
172    }
173
174    #[test]
175    fn average_raise() {
176        let cfg = default_forehead_raise_config();
177        let mut s = new_forehead_raise_state();
178        fhr_set_all(&mut s, &cfg, 0.9);
179        assert!((fhr_average_raise(&s) - 0.9).abs() < 1e-6);
180    }
181
182    #[test]
183    fn reset_clears() {
184        let cfg = default_forehead_raise_config();
185        let mut s = new_forehead_raise_state();
186        fhr_set_all(&mut s, &cfg, 0.5);
187        fhr_reset(&mut s);
188        assert!(fhr_is_neutral(&s));
189    }
190
191    #[test]
192    fn blend_midpoint() {
193        let a = new_forehead_raise_state();
194        let cfg = default_forehead_raise_config();
195        let mut b = new_forehead_raise_state();
196        fhr_set_all(&mut b, &cfg, 1.0);
197        let mid = fhr_blend(&a, &b, 0.5);
198        assert!((mid.center_raise - 0.5).abs() < 1e-6);
199    }
200
201    #[test]
202    fn to_weights_count() {
203        let s = new_forehead_raise_state();
204        assert_eq!(fhr_to_weights(&s).len(), 4);
205    }
206
207    #[test]
208    fn to_json_fields() {
209        let s = new_forehead_raise_state();
210        let j = fhr_to_json(&s);
211        assert!(j.contains("center_raise"));
212        assert!(j.contains("skin_tension"));
213    }
214}