Skip to main content

oxihuman_morph/
forehead_vein_control.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! Forehead vein (temporal / supraorbital vein) prominence control.
6
7/// State.
8#[allow(dead_code)]
9#[derive(Clone, Debug)]
10pub struct ForeheadVeinState {
11    /// Temple vein prominence (0..1).
12    pub temple_left: f32,
13    pub temple_right: f32,
14    /// Central supraorbital vein (0..1).
15    pub central: f32,
16}
17
18/// Config.
19#[allow(dead_code)]
20#[derive(Clone, Debug)]
21pub struct ForeheadVeinConfig {
22    pub max_prominence: f32,
23}
24
25impl Default for ForeheadVeinConfig {
26    fn default() -> Self {
27        Self {
28            max_prominence: 1.0,
29        }
30    }
31}
32impl Default for ForeheadVeinState {
33    fn default() -> Self {
34        Self {
35            temple_left: 0.0,
36            temple_right: 0.0,
37            central: 0.0,
38        }
39    }
40}
41
42#[allow(dead_code)]
43pub fn new_forehead_vein_state() -> ForeheadVeinState {
44    ForeheadVeinState::default()
45}
46
47#[allow(dead_code)]
48pub fn default_forehead_vein_config() -> ForeheadVeinConfig {
49    ForeheadVeinConfig::default()
50}
51
52#[allow(dead_code)]
53pub fn fv_set_temple(
54    state: &mut ForeheadVeinState,
55    cfg: &ForeheadVeinConfig,
56    left: f32,
57    right: f32,
58) {
59    state.temple_left = left.clamp(0.0, cfg.max_prominence);
60    state.temple_right = right.clamp(0.0, cfg.max_prominence);
61}
62
63#[allow(dead_code)]
64pub fn fv_set_central(state: &mut ForeheadVeinState, cfg: &ForeheadVeinConfig, v: f32) {
65    state.central = v.clamp(0.0, cfg.max_prominence);
66}
67
68#[allow(dead_code)]
69pub fn fv_reset(state: &mut ForeheadVeinState) {
70    *state = ForeheadVeinState::default();
71}
72
73#[allow(dead_code)]
74pub fn fv_is_neutral(state: &ForeheadVeinState) -> bool {
75    state.temple_left < 1e-4 && state.temple_right < 1e-4 && state.central < 1e-4
76}
77
78#[allow(dead_code)]
79pub fn fv_blend(a: &ForeheadVeinState, b: &ForeheadVeinState, t: f32) -> ForeheadVeinState {
80    let t = t.clamp(0.0, 1.0);
81    ForeheadVeinState {
82        temple_left: a.temple_left + (b.temple_left - a.temple_left) * t,
83        temple_right: a.temple_right + (b.temple_right - a.temple_right) * t,
84        central: a.central + (b.central - a.central) * t,
85    }
86}
87
88#[allow(dead_code)]
89pub fn fv_total_prominence(state: &ForeheadVeinState) -> f32 {
90    (state.temple_left + state.temple_right + state.central) / 3.0
91}
92
93#[allow(dead_code)]
94pub fn fv_to_weights(state: &ForeheadVeinState) -> [f32; 3] {
95    [state.temple_left, state.temple_right, state.central]
96}
97
98#[allow(dead_code)]
99pub fn fv_to_json(state: &ForeheadVeinState) -> String {
100    format!(
101        "{{\"temple_l\":{:.4},\"temple_r\":{:.4},\"central\":{:.4}}}",
102        state.temple_left, state.temple_right, state.central
103    )
104}
105
106#[allow(dead_code)]
107pub fn fv_symmetry(state: &ForeheadVeinState) -> f32 {
108    1.0 - (state.temple_left - state.temple_right).abs().min(1.0)
109}
110
111#[cfg(test)]
112mod tests {
113    use super::*;
114
115    #[test]
116    fn default_neutral() {
117        assert!(fv_is_neutral(&new_forehead_vein_state()));
118    }
119
120    #[test]
121    fn set_temple_clamps() {
122        let mut s = new_forehead_vein_state();
123        let cfg = default_forehead_vein_config();
124        fv_set_temple(&mut s, &cfg, 2.0, -1.0);
125        assert!(s.temple_left <= cfg.max_prominence);
126        assert!(s.temple_right >= 0.0);
127    }
128
129    #[test]
130    fn central_clamp() {
131        let mut s = new_forehead_vein_state();
132        let cfg = default_forehead_vein_config();
133        fv_set_central(&mut s, &cfg, 5.0);
134        assert!(s.central <= cfg.max_prominence);
135    }
136
137    #[test]
138    fn reset_neutral() {
139        let mut s = new_forehead_vein_state();
140        let cfg = default_forehead_vein_config();
141        fv_set_temple(&mut s, &cfg, 0.5, 0.5);
142        fv_reset(&mut s);
143        assert!(fv_is_neutral(&s));
144    }
145
146    #[test]
147    fn blend_midpoint() {
148        let cfg = default_forehead_vein_config();
149        let mut a = new_forehead_vein_state();
150        let mut b = new_forehead_vein_state();
151        fv_set_central(&mut a, &cfg, 0.0);
152        fv_set_central(&mut b, &cfg, 1.0);
153        let m = fv_blend(&a, &b, 0.5);
154        assert!((m.central - 0.5).abs() < 1e-4);
155    }
156
157    #[test]
158    fn total_prominence_zero() {
159        assert!((fv_total_prominence(&new_forehead_vein_state())).abs() < 1e-5);
160    }
161
162    #[test]
163    fn symmetry_one_equal() {
164        let s = new_forehead_vein_state();
165        assert!((fv_symmetry(&s) - 1.0).abs() < 1e-5);
166    }
167
168    #[test]
169    fn weights_len() {
170        assert_eq!(fv_to_weights(&new_forehead_vein_state()).len(), 3);
171    }
172
173    #[test]
174    fn json_has_central() {
175        assert!(fv_to_json(&new_forehead_vein_state()).contains("central"));
176    }
177
178    #[test]
179    fn not_neutral_after_set() {
180        let mut s = new_forehead_vein_state();
181        let cfg = default_forehead_vein_config();
182        fv_set_central(&mut s, &cfg, 0.5);
183        assert!(!fv_is_neutral(&s));
184    }
185}