Skip to main content

oxihuman_morph/
vitiligo_morph.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! Vitiligo depigmentation morph stub.
6
7/// Vitiligo distribution pattern.
8#[derive(Debug, Clone, Copy, PartialEq)]
9pub enum VitiligoPaattern {
10    Focal,
11    Segmental,
12    Generalized,
13    Universal,
14}
15
16/// A vitiligo patch entry.
17#[derive(Debug, Clone)]
18pub struct VitilligoPatch {
19    pub position: [f32; 3],
20    pub radius: f32,
21    pub depigmentation: f32,
22}
23
24/// Vitiligo morph controller.
25#[derive(Debug, Clone)]
26pub struct VitiligoMorph {
27    pub pattern: VitiligoPaattern,
28    pub patches: Vec<VitilligoPatch>,
29    pub global_extent: f32,
30    pub morph_count: usize,
31    pub enabled: bool,
32}
33
34impl VitiligoMorph {
35    pub fn new(morph_count: usize) -> Self {
36        VitiligoMorph {
37            pattern: VitiligoPaattern::Focal,
38            patches: Vec::new(),
39            global_extent: 0.0,
40            morph_count,
41            enabled: true,
42        }
43    }
44}
45
46/// Create a new vitiligo morph.
47pub fn new_vitiligo_morph(morph_count: usize) -> VitiligoMorph {
48    VitiligoMorph::new(morph_count)
49}
50
51/// Set vitiligo pattern.
52pub fn vim_set_pattern(morph: &mut VitiligoMorph, pattern: VitiligoPaattern) {
53    morph.pattern = pattern;
54}
55
56/// Add a depigmentation patch.
57pub fn vim_add_patch(morph: &mut VitiligoMorph, patch: VitilligoPatch) {
58    morph.patches.push(patch);
59}
60
61/// Set global extent (fraction of body affected).
62pub fn vim_set_extent(morph: &mut VitiligoMorph, extent: f32) {
63    morph.global_extent = extent.clamp(0.0, 1.0);
64}
65
66/// Clear all patches.
67pub fn vim_clear(morph: &mut VitiligoMorph) {
68    morph.patches.clear();
69}
70
71/// Evaluate morph weights (stub: uniform from global_extent).
72pub fn vim_evaluate(morph: &VitiligoMorph) -> Vec<f32> {
73    /* Stub: uniform weight from global_extent */
74    if !morph.enabled || morph.morph_count == 0 {
75        return vec![];
76    }
77    vec![morph.global_extent; morph.morph_count]
78}
79
80/// Enable or disable.
81pub fn vim_set_enabled(morph: &mut VitiligoMorph, enabled: bool) {
82    morph.enabled = enabled;
83}
84
85/// Return patch count.
86pub fn vim_patch_count(morph: &VitiligoMorph) -> usize {
87    morph.patches.len()
88}
89
90/// Serialize to JSON-like string.
91pub fn vim_to_json(morph: &VitiligoMorph) -> String {
92    let pat = match morph.pattern {
93        VitiligoPaattern::Focal => "focal",
94        VitiligoPaattern::Segmental => "segmental",
95        VitiligoPaattern::Generalized => "generalized",
96        VitiligoPaattern::Universal => "universal",
97    };
98    format!(
99        r#"{{"pattern":"{}","patch_count":{},"global_extent":{},"morph_count":{},"enabled":{}}}"#,
100        pat,
101        morph.patches.len(),
102        morph.global_extent,
103        morph.morph_count,
104        morph.enabled
105    )
106}
107
108#[cfg(test)]
109mod tests {
110    use super::*;
111
112    fn make_patch() -> VitilligoPatch {
113        VitilligoPatch {
114            position: [0.2, 0.5, 0.1],
115            radius: 0.05,
116            depigmentation: 1.0,
117        }
118    }
119
120    #[test]
121    fn test_default_pattern() {
122        let m = new_vitiligo_morph(4);
123        assert_eq!(
124            m.pattern,
125            VitiligoPaattern::Focal /* default pattern must be Focal */
126        );
127    }
128
129    #[test]
130    fn test_set_pattern() {
131        let mut m = new_vitiligo_morph(4);
132        vim_set_pattern(&mut m, VitiligoPaattern::Universal);
133        assert_eq!(
134            m.pattern,
135            VitiligoPaattern::Universal /* pattern must be set */
136        );
137    }
138
139    #[test]
140    fn test_add_patch() {
141        let mut m = new_vitiligo_morph(4);
142        vim_add_patch(&mut m, make_patch());
143        assert_eq!(vim_patch_count(&m), 1 /* one patch after add */);
144    }
145
146    #[test]
147    fn test_clear() {
148        let mut m = new_vitiligo_morph(4);
149        vim_add_patch(&mut m, make_patch());
150        vim_clear(&mut m);
151        assert_eq!(vim_patch_count(&m), 0 /* cleared */);
152    }
153
154    #[test]
155    fn test_extent_clamp() {
156        let mut m = new_vitiligo_morph(4);
157        vim_set_extent(&mut m, 1.5);
158        assert!((m.global_extent - 1.0).abs() < 1e-6 /* extent clamped to 1.0 */);
159    }
160
161    #[test]
162    fn test_evaluate_length() {
163        let mut m = new_vitiligo_morph(6);
164        vim_set_extent(&mut m, 0.5);
165        assert_eq!(
166            vim_evaluate(&m).len(),
167            6 /* output must match morph_count */
168        );
169    }
170
171    #[test]
172    fn test_evaluate_disabled() {
173        let mut m = new_vitiligo_morph(4);
174        vim_set_enabled(&mut m, false);
175        assert!(vim_evaluate(&m).is_empty() /* disabled must return empty */);
176    }
177
178    #[test]
179    fn test_to_json_has_pattern() {
180        let m = new_vitiligo_morph(4);
181        let j = vim_to_json(&m);
182        assert!(j.contains("\"pattern\"") /* JSON must have pattern */);
183    }
184
185    #[test]
186    fn test_enabled_default() {
187        let m = new_vitiligo_morph(4);
188        assert!(m.enabled /* must be enabled by default */);
189    }
190
191    #[test]
192    fn test_evaluate_matches_extent() {
193        let mut m = new_vitiligo_morph(3);
194        vim_set_extent(&mut m, 0.5);
195        let out = vim_evaluate(&m);
196        assert!((out[0] - 0.5).abs() < 1e-5 /* evaluate must match global_extent */);
197    }
198}