oxihuman_morph/
skin_fold_control.rs1#![allow(dead_code)]
3
4#[allow(dead_code)]
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum FoldSite {
10 ElbowInner,
11 ElbowOuter,
12 KneeInner,
13 KneeOuter,
14 ArmPit,
15 Groin,
16 NeckBase,
17 WristInner,
18}
19
20#[allow(dead_code)]
22#[derive(Debug, Clone, PartialEq)]
23pub struct SkinFoldConfig {
24 pub depth_scale: f32,
26 pub width_scale: f32,
28}
29
30impl Default for SkinFoldConfig {
31 fn default() -> Self {
32 Self {
33 depth_scale: 0.005,
34 width_scale: 0.012,
35 }
36 }
37}
38
39#[allow(dead_code)]
41#[derive(Debug, Clone, Default)]
42pub struct SkinFoldState {
43 folds: Vec<(FoldSite, f32)>,
44}
45
46#[allow(dead_code)]
47pub fn new_skin_fold_state() -> SkinFoldState {
48 SkinFoldState::default()
49}
50
51#[allow(dead_code)]
52pub fn default_skin_fold_config() -> SkinFoldConfig {
53 SkinFoldConfig::default()
54}
55
56#[allow(dead_code)]
57pub fn sf_set(state: &mut SkinFoldState, site: FoldSite, weight: f32) {
58 let weight = weight.clamp(0.0, 1.0);
59 if let Some(entry) = state.folds.iter_mut().find(|(s, _)| *s == site) {
60 entry.1 = weight;
61 } else {
62 state.folds.push((site, weight));
63 }
64}
65
66#[allow(dead_code)]
67pub fn sf_get(state: &SkinFoldState, site: FoldSite) -> f32 {
68 state
69 .folds
70 .iter()
71 .find(|(s, _)| *s == site)
72 .map_or(0.0, |(_, w)| *w)
73}
74
75#[allow(dead_code)]
76pub fn sf_reset(state: &mut SkinFoldState) {
77 state.folds.clear();
78}
79
80#[allow(dead_code)]
81pub fn sf_is_neutral(state: &SkinFoldState) -> bool {
82 state.folds.iter().all(|(_, w)| *w < 1e-4)
83}
84
85#[allow(dead_code)]
87pub fn sf_active_count(state: &SkinFoldState) -> usize {
88 state.folds.iter().filter(|(_, w)| *w > 1e-4).count()
89}
90
91#[allow(dead_code)]
93pub fn sf_depth_m(state: &SkinFoldState, site: FoldSite, cfg: &SkinFoldConfig) -> f32 {
94 sf_get(state, site) * cfg.depth_scale
95}
96
97#[allow(dead_code)]
99pub fn sf_width_m(state: &SkinFoldState, site: FoldSite, cfg: &SkinFoldConfig) -> f32 {
100 sf_get(state, site) * cfg.width_scale
101}
102
103#[allow(dead_code)]
104pub fn sf_blend(a: &SkinFoldState, b: &SkinFoldState, t: f32) -> SkinFoldState {
105 let t = t.clamp(0.0, 1.0);
106 let inv = 1.0 - t;
107 let mut result = SkinFoldState::default();
108 for &(site, wa) in &a.folds {
109 let wb = sf_get(b, site);
110 result.folds.push((site, wa * inv + wb * t));
111 }
112 result
113}
114
115#[allow(dead_code)]
116pub fn sf_to_json(state: &SkinFoldState) -> String {
117 format!(
118 "{{\"site_count\":{},\"active\":{}}}",
119 state.folds.len(),
120 sf_active_count(state)
121 )
122}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127
128 #[test]
129 fn default_neutral() {
130 assert!(sf_is_neutral(&new_skin_fold_state()));
131 }
132
133 #[test]
134 fn set_and_get() {
135 let mut s = new_skin_fold_state();
136 sf_set(&mut s, FoldSite::ElbowInner, 0.7);
137 assert!((sf_get(&s, FoldSite::ElbowInner) - 0.7).abs() < 1e-6);
138 }
139
140 #[test]
141 fn clamps_high() {
142 let mut s = new_skin_fold_state();
143 sf_set(&mut s, FoldSite::KneeInner, 5.0);
144 assert!((sf_get(&s, FoldSite::KneeInner) - 1.0).abs() < 1e-6);
145 }
146
147 #[test]
148 fn clamps_low() {
149 let mut s = new_skin_fold_state();
150 sf_set(&mut s, FoldSite::KneeOuter, -2.0);
151 assert!(sf_get(&s, FoldSite::KneeOuter) < 1e-6);
152 }
153
154 #[test]
155 fn reset_clears() {
156 let mut s = new_skin_fold_state();
157 sf_set(&mut s, FoldSite::ArmPit, 1.0);
158 sf_reset(&mut s);
159 assert!(sf_is_neutral(&s));
160 }
161
162 #[test]
163 fn active_count() {
164 let mut s = new_skin_fold_state();
165 sf_set(&mut s, FoldSite::Groin, 0.5);
166 sf_set(&mut s, FoldSite::NeckBase, 0.0);
167 assert_eq!(sf_active_count(&s), 1);
168 }
169
170 #[test]
171 fn depth_nonzero_when_active() {
172 let cfg = default_skin_fold_config();
173 let mut s = new_skin_fold_state();
174 sf_set(&mut s, FoldSite::WristInner, 1.0);
175 assert!(sf_depth_m(&s, FoldSite::WristInner, &cfg) > 0.0);
176 }
177
178 #[test]
179 fn blend_midpoint() {
180 let mut a = new_skin_fold_state();
181 sf_set(&mut a, FoldSite::ElbowOuter, 1.0);
182 let b = new_skin_fold_state();
183 let r = sf_blend(&a, &b, 0.5);
184 assert!((sf_get(&r, FoldSite::ElbowOuter) - 0.5).abs() < 1e-5);
185 }
186
187 #[test]
188 fn update_existing_entry() {
189 let mut s = new_skin_fold_state();
190 sf_set(&mut s, FoldSite::ElbowInner, 0.3);
191 sf_set(&mut s, FoldSite::ElbowInner, 0.9);
192 assert_eq!(s.folds.len(), 1);
193 }
194
195 #[test]
196 fn json_has_keys() {
197 let j = sf_to_json(&new_skin_fold_state());
198 assert!(j.contains("site_count") && j.contains("active"));
199 }
200}