Skip to main content

oxihuman_morph/
finger_length_control.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan) / SPDX-License-Identifier: Apache-2.0 / #![allow(dead_code)]
2#![allow(dead_code)]
3
4//! Finger length morphology controls for individual finger proportions.
5
6use std::f32::consts::PI;
7
8#[allow(dead_code)]
9#[derive(Debug, Clone)]
10pub struct FingerLengthConfig {
11    pub overall_length: f32,
12    pub thickness: f32,
13    pub taper: f32,
14}
15
16#[allow(dead_code)]
17#[derive(Debug, Clone)]
18pub struct FingerLengthState {
19    pub overall_length: f32,
20    pub thickness: f32,
21    pub taper: f32,
22    pub knuckle_size: f32,
23}
24
25#[allow(dead_code)]
26#[derive(Debug, Clone)]
27pub struct FingerLengthWeights {
28    pub long: f32,
29    pub short: f32,
30    pub thick: f32,
31    pub thin: f32,
32    pub tapered: f32,
33    pub knobby: f32,
34}
35
36#[allow(dead_code)]
37pub fn default_finger_length_config() -> FingerLengthConfig {
38    FingerLengthConfig {
39        overall_length: 0.5,
40        thickness: 0.5,
41        taper: 0.5,
42    }
43}
44
45#[allow(dead_code)]
46pub fn new_finger_length_state() -> FingerLengthState {
47    FingerLengthState {
48        overall_length: 0.5,
49        thickness: 0.5,
50        taper: 0.5,
51        knuckle_size: 0.3,
52    }
53}
54
55#[allow(dead_code)]
56pub fn set_finger_overall_length(state: &mut FingerLengthState, value: f32) {
57    state.overall_length = value.clamp(0.0, 1.0);
58}
59
60#[allow(dead_code)]
61pub fn set_finger_thickness(state: &mut FingerLengthState, value: f32) {
62    state.thickness = value.clamp(0.0, 1.0);
63}
64
65#[allow(dead_code)]
66pub fn set_finger_taper(state: &mut FingerLengthState, value: f32) {
67    state.taper = value.clamp(0.0, 1.0);
68}
69
70#[allow(dead_code)]
71pub fn set_finger_knuckle_size(state: &mut FingerLengthState, value: f32) {
72    state.knuckle_size = value.clamp(0.0, 1.0);
73}
74
75#[allow(dead_code)]
76pub fn compute_finger_length_weights(
77    state: &FingerLengthState,
78    cfg: &FingerLengthConfig,
79) -> FingerLengthWeights {
80    let l = state.overall_length * cfg.overall_length;
81    let long = (l * (PI * 0.25).sin()).clamp(0.0, 1.0);
82    let short = (1.0 - l).clamp(0.0, 1.0);
83    let thick = (state.thickness * cfg.thickness).clamp(0.0, 1.0);
84    let thin = (1.0 - thick).clamp(0.0, 1.0);
85    let tapered = (state.taper * cfg.taper).clamp(0.0, 1.0);
86    let knobby = state.knuckle_size.clamp(0.0, 1.0);
87    FingerLengthWeights {
88        long,
89        short,
90        thick,
91        thin,
92        tapered,
93        knobby,
94    }
95}
96
97#[allow(dead_code)]
98pub fn finger_length_to_json(state: &FingerLengthState) -> String {
99    format!(
100        r#"{{"overall_length":{},"thickness":{},"taper":{},"knuckle_size":{}}}"#,
101        state.overall_length, state.thickness, state.taper, state.knuckle_size
102    )
103}
104
105#[allow(dead_code)]
106pub fn blend_finger_lengths(
107    a: &FingerLengthState,
108    b: &FingerLengthState,
109    t: f32,
110) -> FingerLengthState {
111    let t = t.clamp(0.0, 1.0);
112    FingerLengthState {
113        overall_length: a.overall_length + (b.overall_length - a.overall_length) * t,
114        thickness: a.thickness + (b.thickness - a.thickness) * t,
115        taper: a.taper + (b.taper - a.taper) * t,
116        knuckle_size: a.knuckle_size + (b.knuckle_size - a.knuckle_size) * t,
117    }
118}
119
120#[cfg(test)]
121mod tests {
122    use super::*;
123
124    #[test]
125    fn test_default_config() {
126        let cfg = default_finger_length_config();
127        assert!((0.0..=1.0).contains(&cfg.overall_length));
128    }
129
130    #[test]
131    fn test_new_state() {
132        let s = new_finger_length_state();
133        assert!((s.overall_length - 0.5).abs() < 1e-6);
134    }
135
136    #[test]
137    fn test_set_length_clamp() {
138        let mut s = new_finger_length_state();
139        set_finger_overall_length(&mut s, 1.5);
140        assert!((s.overall_length - 1.0).abs() < 1e-6);
141    }
142
143    #[test]
144    fn test_set_thickness() {
145        let mut s = new_finger_length_state();
146        set_finger_thickness(&mut s, 0.8);
147        assert!((s.thickness - 0.8).abs() < 1e-6);
148    }
149
150    #[test]
151    fn test_set_taper() {
152        let mut s = new_finger_length_state();
153        set_finger_taper(&mut s, 0.7);
154        assert!((s.taper - 0.7).abs() < 1e-6);
155    }
156
157    #[test]
158    fn test_set_knuckle_size() {
159        let mut s = new_finger_length_state();
160        set_finger_knuckle_size(&mut s, 0.6);
161        assert!((s.knuckle_size - 0.6).abs() < 1e-6);
162    }
163
164    #[test]
165    fn test_compute_weights() {
166        let s = new_finger_length_state();
167        let cfg = default_finger_length_config();
168        let w = compute_finger_length_weights(&s, &cfg);
169        assert!((0.0..=1.0).contains(&w.long));
170        assert!((0.0..=1.0).contains(&w.thick));
171    }
172
173    #[test]
174    fn test_to_json() {
175        let s = new_finger_length_state();
176        let json = finger_length_to_json(&s);
177        assert!(json.contains("overall_length"));
178        assert!(json.contains("knuckle_size"));
179    }
180
181    #[test]
182    fn test_blend() {
183        let a = new_finger_length_state();
184        let mut b = new_finger_length_state();
185        b.overall_length = 1.0;
186        let mid = blend_finger_lengths(&a, &b, 0.5);
187        assert!((mid.overall_length - 0.75).abs() < 1e-6);
188    }
189
190    #[test]
191    fn test_blend_identity() {
192        let a = new_finger_length_state();
193        let r = blend_finger_lengths(&a, &a, 0.5);
194        assert!((r.overall_length - a.overall_length).abs() < 1e-6);
195    }
196}