oxihuman_morph/
hand_v2.rs1#![allow(dead_code)]
4
5#[allow(dead_code)]
9#[derive(Debug, Clone)]
10pub struct HandV2Params {
11 pub dorsum_thickness: f32,
12 pub knuckle_prominence: f32,
13 pub vein_raise: f32,
14 pub tendon_visibility: f32,
15}
16
17impl Default for HandV2Params {
18 fn default() -> Self {
19 HandV2Params {
20 dorsum_thickness: 0.0,
21 knuckle_prominence: 0.0,
22 vein_raise: 0.0,
23 tendon_visibility: 0.0,
24 }
25 }
26}
27
28#[allow(dead_code)]
29pub fn default_hand_v2_params() -> HandV2Params {
30 HandV2Params::default()
31}
32
33#[allow(dead_code)]
34pub fn hv2_set_dorsum(p: &mut HandV2Params, v: f32) {
35 p.dorsum_thickness = v.clamp(-1.0, 1.0);
36}
37
38#[allow(dead_code)]
39pub fn hv2_set_knuckle(p: &mut HandV2Params, v: f32) {
40 p.knuckle_prominence = v.clamp(0.0, 1.0);
41}
42
43#[allow(dead_code)]
44pub fn hv2_set_vein(p: &mut HandV2Params, v: f32) {
45 p.vein_raise = v.clamp(0.0, 1.0);
46}
47
48#[allow(dead_code)]
49pub fn hv2_set_tendon(p: &mut HandV2Params, v: f32) {
50 p.tendon_visibility = v.clamp(0.0, 1.0);
51}
52
53#[allow(dead_code)]
54pub fn hv2_reset(p: &mut HandV2Params) {
55 *p = HandV2Params::default();
56}
57
58#[allow(dead_code)]
59pub fn hv2_is_neutral(p: &HandV2Params) -> bool {
60 p.dorsum_thickness.abs() < 1e-6
61 && p.knuckle_prominence.abs() < 1e-6
62 && p.vein_raise.abs() < 1e-6
63 && p.tendon_visibility.abs() < 1e-6
64}
65
66#[allow(dead_code)]
67pub fn hv2_blend(a: &HandV2Params, b: &HandV2Params, t: f32) -> HandV2Params {
68 let t = t.clamp(0.0, 1.0);
69 HandV2Params {
70 dorsum_thickness: a.dorsum_thickness + (b.dorsum_thickness - a.dorsum_thickness) * t,
71 knuckle_prominence: a.knuckle_prominence
72 + (b.knuckle_prominence - a.knuckle_prominence) * t,
73 vein_raise: a.vein_raise + (b.vein_raise - a.vein_raise) * t,
74 tendon_visibility: a.tendon_visibility + (b.tendon_visibility - a.tendon_visibility) * t,
75 }
76}
77
78#[allow(dead_code)]
79pub fn hv2_surface_detail_estimate(p: &HandV2Params) -> f32 {
80 p.knuckle_prominence * 0.5 + p.vein_raise * 0.3 + p.tendon_visibility * 0.2
81}
82
83#[allow(dead_code)]
84pub fn hv2_to_json(p: &HandV2Params) -> String {
85 format!(
86 r#"{{"dorsum_thickness":{:.4},"knuckle_prominence":{:.4},"vein_raise":{:.4},"tendon_visibility":{:.4}}}"#,
87 p.dorsum_thickness, p.knuckle_prominence, p.vein_raise, p.tendon_visibility
88 )
89}
90
91#[cfg(test)]
92mod tests {
93 use super::*;
94
95 #[test]
96 fn default_is_neutral() {
97 assert!(hv2_is_neutral(&default_hand_v2_params()));
98 }
99
100 #[test]
101 fn set_dorsum_clamps() {
102 let mut p = default_hand_v2_params();
103 hv2_set_dorsum(&mut p, 5.0);
104 assert!((p.dorsum_thickness - 1.0).abs() < 1e-6);
105 }
106
107 #[test]
108 fn set_knuckle_clamps() {
109 let mut p = default_hand_v2_params();
110 hv2_set_knuckle(&mut p, -1.0);
111 assert!(p.knuckle_prominence.abs() < 1e-6);
112 }
113
114 #[test]
115 fn set_vein_positive() {
116 let mut p = default_hand_v2_params();
117 hv2_set_vein(&mut p, 0.7);
118 assert!((p.vein_raise - 0.7).abs() < 1e-5);
119 }
120
121 #[test]
122 fn reset_clears() {
123 let mut p = default_hand_v2_params();
124 hv2_set_knuckle(&mut p, 0.9);
125 hv2_reset(&mut p);
126 assert!(hv2_is_neutral(&p));
127 }
128
129 #[test]
130 fn blend_midpoint() {
131 let a = default_hand_v2_params();
132 let mut b = default_hand_v2_params();
133 hv2_set_knuckle(&mut b, 1.0);
134 let m = hv2_blend(&a, &b, 0.5);
135 assert!((m.knuckle_prominence - 0.5).abs() < 1e-5);
136 }
137
138 #[test]
139 fn surface_detail_zero_when_neutral() {
140 let p = default_hand_v2_params();
141 assert!(hv2_surface_detail_estimate(&p).abs() < 1e-6);
142 }
143
144 #[test]
145 fn surface_detail_positive() {
146 let mut p = default_hand_v2_params();
147 hv2_set_knuckle(&mut p, 1.0);
148 assert!(hv2_surface_detail_estimate(&p) > 0.0);
149 }
150
151 #[test]
152 fn to_json_has_fields() {
153 let p = default_hand_v2_params();
154 let j = hv2_to_json(&p);
155 assert!(j.contains("dorsum_thickness"));
156 assert!(j.contains("knuckle_prominence"));
157 }
158}