Skip to main content

oxihuman_morph/
hand_vein_control.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! Hand vein morph — controls prominence and pattern of dorsal hand veins.
6
7/// Configuration for hand vein control.
8#[allow(dead_code)]
9#[derive(Debug, Clone)]
10pub struct HandVeinConfig {
11    pub max_prominence: f32,
12}
13
14/// Runtime state.
15#[allow(dead_code)]
16#[derive(Debug, Clone)]
17pub struct HandVeinState {
18    pub left_prominence: f32,
19    pub right_prominence: f32,
20    pub left_branching: f32,
21    pub right_branching: f32,
22}
23
24#[allow(dead_code)]
25pub fn default_hand_vein_config() -> HandVeinConfig {
26    HandVeinConfig {
27        max_prominence: 1.0,
28    }
29}
30
31#[allow(dead_code)]
32pub fn new_hand_vein_state() -> HandVeinState {
33    HandVeinState {
34        left_prominence: 0.0,
35        right_prominence: 0.0,
36        left_branching: 0.0,
37        right_branching: 0.0,
38    }
39}
40
41#[allow(dead_code)]
42pub fn hv_set_left(state: &mut HandVeinState, cfg: &HandVeinConfig, v: f32) {
43    state.left_prominence = v.clamp(0.0, cfg.max_prominence);
44}
45
46#[allow(dead_code)]
47pub fn hv_set_right(state: &mut HandVeinState, cfg: &HandVeinConfig, v: f32) {
48    state.right_prominence = v.clamp(0.0, cfg.max_prominence);
49}
50
51#[allow(dead_code)]
52pub fn hv_set_both(state: &mut HandVeinState, cfg: &HandVeinConfig, v: f32) {
53    let clamped = v.clamp(0.0, cfg.max_prominence);
54    state.left_prominence = clamped;
55    state.right_prominence = clamped;
56}
57
58#[allow(dead_code)]
59pub fn hv_set_branching(state: &mut HandVeinState, left: f32, right: f32) {
60    state.left_branching = left.clamp(0.0, 1.0);
61    state.right_branching = right.clamp(0.0, 1.0);
62}
63
64#[allow(dead_code)]
65pub fn hv_reset(state: &mut HandVeinState) {
66    *state = new_hand_vein_state();
67}
68
69#[allow(dead_code)]
70pub fn hv_is_neutral(state: &HandVeinState) -> bool {
71    let vals = [
72        state.left_prominence,
73        state.right_prominence,
74        state.left_branching,
75        state.right_branching,
76    ];
77    !vals.is_empty() && vals.iter().all(|v| v.abs() < 1e-6)
78}
79
80#[allow(dead_code)]
81pub fn hv_average_prominence(state: &HandVeinState) -> f32 {
82    (state.left_prominence + state.right_prominence) * 0.5
83}
84
85#[allow(dead_code)]
86pub fn hv_symmetry(state: &HandVeinState) -> f32 {
87    (state.left_prominence - state.right_prominence).abs()
88}
89
90#[allow(dead_code)]
91pub fn hv_blend(a: &HandVeinState, b: &HandVeinState, t: f32) -> HandVeinState {
92    let t = t.clamp(0.0, 1.0);
93    HandVeinState {
94        left_prominence: a.left_prominence + (b.left_prominence - a.left_prominence) * t,
95        right_prominence: a.right_prominence + (b.right_prominence - a.right_prominence) * t,
96        left_branching: a.left_branching + (b.left_branching - a.left_branching) * t,
97        right_branching: a.right_branching + (b.right_branching - a.right_branching) * t,
98    }
99}
100
101#[allow(dead_code)]
102pub fn hv_to_weights(state: &HandVeinState) -> Vec<(String, f32)> {
103    vec![
104        ("hand_vein_l".to_string(), state.left_prominence),
105        ("hand_vein_r".to_string(), state.right_prominence),
106        ("hand_vein_branch_l".to_string(), state.left_branching),
107        ("hand_vein_branch_r".to_string(), state.right_branching),
108    ]
109}
110
111#[allow(dead_code)]
112pub fn hv_to_json(state: &HandVeinState) -> String {
113    format!(
114        r#"{{"left_prominence":{:.4},"right_prominence":{:.4},"left_branching":{:.4},"right_branching":{:.4}}}"#,
115        state.left_prominence, state.right_prominence, state.left_branching, state.right_branching
116    )
117}
118
119#[cfg(test)]
120mod tests {
121    use super::*;
122
123    #[test]
124    fn default_config() {
125        let cfg = default_hand_vein_config();
126        assert!((cfg.max_prominence - 1.0).abs() < 1e-6);
127    }
128
129    #[test]
130    fn new_state_neutral() {
131        let s = new_hand_vein_state();
132        assert!(hv_is_neutral(&s));
133    }
134
135    #[test]
136    fn set_left_clamps() {
137        let cfg = default_hand_vein_config();
138        let mut s = new_hand_vein_state();
139        hv_set_left(&mut s, &cfg, 5.0);
140        assert!((s.left_prominence - 1.0).abs() < 1e-6);
141    }
142
143    #[test]
144    fn set_both_equal() {
145        let cfg = default_hand_vein_config();
146        let mut s = new_hand_vein_state();
147        hv_set_both(&mut s, &cfg, 0.6);
148        assert!((hv_symmetry(&s)).abs() < 1e-6);
149    }
150
151    #[test]
152    fn set_branching() {
153        let mut s = new_hand_vein_state();
154        hv_set_branching(&mut s, 0.3, 0.7);
155        assert!((s.left_branching - 0.3).abs() < 1e-6);
156        assert!((s.right_branching - 0.7).abs() < 1e-6);
157    }
158
159    #[test]
160    fn average_prominence() {
161        let cfg = default_hand_vein_config();
162        let mut s = new_hand_vein_state();
163        hv_set_left(&mut s, &cfg, 0.4);
164        hv_set_right(&mut s, &cfg, 0.6);
165        assert!((hv_average_prominence(&s) - 0.5).abs() < 1e-6);
166    }
167
168    #[test]
169    fn reset_clears() {
170        let cfg = default_hand_vein_config();
171        let mut s = new_hand_vein_state();
172        hv_set_both(&mut s, &cfg, 0.8);
173        hv_reset(&mut s);
174        assert!(hv_is_neutral(&s));
175    }
176
177    #[test]
178    fn blend_midpoint() {
179        let a = new_hand_vein_state();
180        let cfg = default_hand_vein_config();
181        let mut b = new_hand_vein_state();
182        hv_set_both(&mut b, &cfg, 1.0);
183        let mid = hv_blend(&a, &b, 0.5);
184        assert!((mid.left_prominence - 0.5).abs() < 1e-6);
185    }
186
187    #[test]
188    fn to_weights_count() {
189        let s = new_hand_vein_state();
190        assert_eq!(hv_to_weights(&s).len(), 4);
191    }
192
193    #[test]
194    fn to_json_fields() {
195        let s = new_hand_vein_state();
196        let j = hv_to_json(&s);
197        assert!(j.contains("left_prominence"));
198        assert!(j.contains("right_branching"));
199    }
200}