Skip to main content

oxihuman_morph/
subcutaneous_fat_morph.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! Subcutaneous fat layer morph stub.
6
7/// Fat distribution pattern.
8#[derive(Debug, Clone, Copy, PartialEq)]
9pub enum FatPattern {
10    Android,
11    Gynoid,
12    Uniform,
13}
14
15/// Subcutaneous fat morph controller.
16#[derive(Debug, Clone)]
17pub struct SubcutaneousFatMorph {
18    pub total_fat: f32,
19    pub pattern: FatPattern,
20    pub visceral_ratio: f32,
21    pub morph_count: usize,
22    pub enabled: bool,
23}
24
25impl SubcutaneousFatMorph {
26    pub fn new(morph_count: usize) -> Self {
27        SubcutaneousFatMorph {
28            total_fat: 0.3,
29            pattern: FatPattern::Uniform,
30            visceral_ratio: 0.2,
31            morph_count,
32            enabled: true,
33        }
34    }
35}
36
37/// Create a new subcutaneous fat morph.
38pub fn new_subcutaneous_fat_morph(morph_count: usize) -> SubcutaneousFatMorph {
39    SubcutaneousFatMorph::new(morph_count)
40}
41
42/// Set total fat level.
43pub fn sfm_set_fat(morph: &mut SubcutaneousFatMorph, fat: f32) {
44    morph.total_fat = fat.clamp(0.0, 1.0);
45}
46
47/// Set distribution pattern.
48pub fn sfm_set_pattern(morph: &mut SubcutaneousFatMorph, pattern: FatPattern) {
49    morph.pattern = pattern;
50}
51
52/// Set visceral-to-subcutaneous ratio.
53pub fn sfm_set_visceral_ratio(morph: &mut SubcutaneousFatMorph, ratio: f32) {
54    morph.visceral_ratio = ratio.clamp(0.0, 1.0);
55}
56
57/// Evaluate morph weights (stub: fat × pattern_scale).
58pub fn sfm_evaluate(morph: &SubcutaneousFatMorph) -> Vec<f32> {
59    /* Stub: pattern modulates fat weight */
60    if !morph.enabled || morph.morph_count == 0 {
61        return vec![];
62    }
63    let scale = match morph.pattern {
64        FatPattern::Android => 0.9,
65        FatPattern::Gynoid => 0.85,
66        FatPattern::Uniform => 1.0,
67    };
68    vec![morph.total_fat * scale; morph.morph_count]
69}
70
71/// Enable or disable.
72pub fn sfm_set_enabled(morph: &mut SubcutaneousFatMorph, enabled: bool) {
73    morph.enabled = enabled;
74}
75
76/// Serialize to JSON-like string.
77pub fn sfm_to_json(morph: &SubcutaneousFatMorph) -> String {
78    let pat = match morph.pattern {
79        FatPattern::Android => "android",
80        FatPattern::Gynoid => "gynoid",
81        FatPattern::Uniform => "uniform",
82    };
83    format!(
84        r#"{{"total_fat":{},"pattern":"{}","visceral_ratio":{},"enabled":{}}}"#,
85        morph.total_fat, pat, morph.visceral_ratio, morph.enabled
86    )
87}
88
89#[cfg(test)]
90mod tests {
91    use super::*;
92
93    #[test]
94    fn test_default_fat() {
95        let m = new_subcutaneous_fat_morph(4);
96        assert!((m.total_fat - 0.3).abs() < 1e-6 /* default fat must be 0.3 */);
97    }
98
99    #[test]
100    fn test_set_fat_clamps() {
101        let mut m = new_subcutaneous_fat_morph(4);
102        sfm_set_fat(&mut m, 1.5);
103        assert!((m.total_fat - 1.0).abs() < 1e-6 /* fat clamped to 1.0 */);
104    }
105
106    #[test]
107    fn test_set_pattern() {
108        let mut m = new_subcutaneous_fat_morph(4);
109        sfm_set_pattern(&mut m, FatPattern::Gynoid);
110        assert_eq!(m.pattern, FatPattern::Gynoid /* pattern must be set */);
111    }
112
113    #[test]
114    fn test_visceral_ratio_clamped() {
115        let mut m = new_subcutaneous_fat_morph(4);
116        sfm_set_visceral_ratio(&mut m, -0.1);
117        assert!((m.visceral_ratio).abs() < 1e-6 /* visceral ratio clamped to 0.0 */);
118    }
119
120    #[test]
121    fn test_evaluate_length() {
122        let m = new_subcutaneous_fat_morph(5);
123        assert_eq!(
124            sfm_evaluate(&m).len(),
125            5 /* output must match morph_count */
126        );
127    }
128
129    #[test]
130    fn test_evaluate_disabled() {
131        let mut m = new_subcutaneous_fat_morph(4);
132        sfm_set_enabled(&mut m, false);
133        assert!(sfm_evaluate(&m).is_empty() /* disabled must return empty */);
134    }
135
136    #[test]
137    fn test_to_json_has_pattern() {
138        let m = new_subcutaneous_fat_morph(4);
139        let j = sfm_to_json(&m);
140        assert!(j.contains("\"pattern\"") /* JSON must have pattern field */);
141    }
142
143    #[test]
144    fn test_enabled_default() {
145        let m = new_subcutaneous_fat_morph(4);
146        assert!(m.enabled /* must be enabled by default */);
147    }
148
149    #[test]
150    fn test_android_scale() {
151        let mut m = new_subcutaneous_fat_morph(2);
152        sfm_set_fat(&mut m, 1.0);
153        sfm_set_pattern(&mut m, FatPattern::Android);
154        let out = sfm_evaluate(&m);
155        assert!((out[0] - 0.9).abs() < 1e-5 /* android pattern applies 0.9 scale */);
156    }
157}