Skip to main content

oxihuman_morph/
skin_thickness_morph.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! Skin thickness deformation morph stub.
6
7/// Body region for skin thickness.
8#[derive(Debug, Clone, Copy, PartialEq)]
9pub enum SkinRegion {
10    Face,
11    Scalp,
12    Hands,
13    Feet,
14    Torso,
15    Limbs,
16}
17
18/// Skin thickness morph controller.
19#[derive(Debug, Clone)]
20pub struct SkinThicknessMorph {
21    pub global_thickness: f32,
22    pub region_values: Vec<(SkinRegion, f32)>,
23    pub morph_count: usize,
24    pub enabled: bool,
25}
26
27impl SkinThicknessMorph {
28    pub fn new(morph_count: usize) -> Self {
29        SkinThicknessMorph {
30            global_thickness: 0.5,
31            region_values: Vec::new(),
32            morph_count,
33            enabled: true,
34        }
35    }
36}
37
38/// Create a new skin thickness morph.
39pub fn new_skin_thickness_morph(morph_count: usize) -> SkinThicknessMorph {
40    SkinThicknessMorph::new(morph_count)
41}
42
43/// Set global skin thickness.
44pub fn stm_set_thickness(morph: &mut SkinThicknessMorph, thickness: f32) {
45    morph.global_thickness = thickness.clamp(0.0, 1.0);
46}
47
48/// Set per-region thickness override.
49pub fn stm_set_region(morph: &mut SkinThicknessMorph, region: SkinRegion, thickness: f32) {
50    let v = thickness.clamp(0.0, 1.0);
51    if let Some(e) = morph.region_values.iter_mut().find(|(r, _)| *r == region) {
52        e.1 = v;
53    } else {
54        morph.region_values.push((region, v));
55    }
56}
57
58/// Evaluate morph weights (stub: uniform from thickness).
59pub fn stm_evaluate(morph: &SkinThicknessMorph) -> Vec<f32> {
60    /* Stub: all targets scaled by global_thickness */
61    if !morph.enabled || morph.morph_count == 0 {
62        return vec![];
63    }
64    vec![morph.global_thickness; morph.morph_count]
65}
66
67/// Enable or disable.
68pub fn stm_set_enabled(morph: &mut SkinThicknessMorph, enabled: bool) {
69    morph.enabled = enabled;
70}
71
72/// Return region count.
73pub fn stm_region_count(morph: &SkinThicknessMorph) -> usize {
74    morph.region_values.len()
75}
76
77/// Serialize to JSON-like string.
78pub fn stm_to_json(morph: &SkinThicknessMorph) -> String {
79    format!(
80        r#"{{"global_thickness":{},"morph_count":{},"regions":{},"enabled":{}}}"#,
81        morph.global_thickness,
82        morph.morph_count,
83        morph.region_values.len(),
84        morph.enabled
85    )
86}
87
88#[cfg(test)]
89mod tests {
90    use super::*;
91
92    #[test]
93    fn test_default_thickness() {
94        let m = new_skin_thickness_morph(4);
95        assert!((m.global_thickness - 0.5).abs() < 1e-6 /* default thickness must be 0.5 */);
96    }
97
98    #[test]
99    fn test_set_thickness_clamps() {
100        let mut m = new_skin_thickness_morph(4);
101        stm_set_thickness(&mut m, -1.0);
102        assert!((m.global_thickness).abs() < 1e-6 /* thickness clamped to 0.0 */);
103    }
104
105    #[test]
106    fn test_region_added() {
107        let mut m = new_skin_thickness_morph(4);
108        stm_set_region(&mut m, SkinRegion::Face, 0.3);
109        assert_eq!(stm_region_count(&m), 1 /* one region must be added */);
110    }
111
112    #[test]
113    fn test_region_updated() {
114        let mut m = new_skin_thickness_morph(4);
115        stm_set_region(&mut m, SkinRegion::Hands, 0.2);
116        stm_set_region(&mut m, SkinRegion::Hands, 0.9);
117        assert_eq!(
118            stm_region_count(&m),
119            1 /* same region must update not duplicate */
120        );
121    }
122
123    #[test]
124    fn test_evaluate_length() {
125        let m = new_skin_thickness_morph(7);
126        let out = stm_evaluate(&m);
127        assert_eq!(out.len(), 7 /* output must match morph_count */);
128    }
129
130    #[test]
131    fn test_evaluate_disabled() {
132        let mut m = new_skin_thickness_morph(4);
133        stm_set_enabled(&mut m, false);
134        assert!(stm_evaluate(&m).is_empty() /* disabled must return empty */);
135    }
136
137    #[test]
138    fn test_to_json_has_global_thickness() {
139        let m = new_skin_thickness_morph(4);
140        let j = stm_to_json(&m);
141        assert!(j.contains("\"global_thickness\"") /* JSON must have global_thickness */);
142    }
143
144    #[test]
145    fn test_enabled_default() {
146        let m = new_skin_thickness_morph(4);
147        assert!(m.enabled /* must be enabled by default */);
148    }
149
150    #[test]
151    fn test_evaluate_value_matches_thickness() {
152        let mut m = new_skin_thickness_morph(3);
153        stm_set_thickness(&mut m, 0.7);
154        let out = stm_evaluate(&m);
155        assert!((out[0] - 0.7).abs() < 1e-5 /* evaluated weight must equal thickness */);
156    }
157}