Skip to main content

oxihuman_morph/
pore_size_morph.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! Skin pore size morph stub.
6
7/// Facial zone for pore size control.
8#[derive(Debug, Clone, Copy, PartialEq)]
9pub enum PoreZone {
10    TZone,
11    Cheeks,
12    Forehead,
13    Chin,
14    Nose,
15}
16
17/// Pore size morph controller.
18#[derive(Debug, Clone)]
19pub struct PoreSizeMorph {
20    pub global_size: f32,
21    pub zone_overrides: Vec<(PoreZone, f32)>,
22    pub depth: f32,
23    pub morph_count: usize,
24    pub enabled: bool,
25}
26
27impl PoreSizeMorph {
28    pub fn new(morph_count: usize) -> Self {
29        PoreSizeMorph {
30            global_size: 0.3,
31            zone_overrides: Vec::new(),
32            depth: 0.5,
33            morph_count,
34            enabled: true,
35        }
36    }
37}
38
39/// Create a new pore size morph controller.
40pub fn new_pore_size_morph(morph_count: usize) -> PoreSizeMorph {
41    PoreSizeMorph::new(morph_count)
42}
43
44/// Set global pore size.
45pub fn psm_set_size(morph: &mut PoreSizeMorph, size: f32) {
46    morph.global_size = size.clamp(0.0, 1.0);
47}
48
49/// Set per-zone size override.
50pub fn psm_set_zone(morph: &mut PoreSizeMorph, zone: PoreZone, size: f32) {
51    let v = size.clamp(0.0, 1.0);
52    if let Some(e) = morph.zone_overrides.iter_mut().find(|(z, _)| *z == zone) {
53        e.1 = v;
54    } else {
55        morph.zone_overrides.push((zone, v));
56    }
57}
58
59/// Set pore depth (affects displacement intensity).
60pub fn psm_set_depth(morph: &mut PoreSizeMorph, depth: f32) {
61    morph.depth = depth.clamp(0.0, 1.0);
62}
63
64/// Evaluate morph weights (stub: size × depth).
65pub fn psm_evaluate(morph: &PoreSizeMorph) -> Vec<f32> {
66    /* Stub: weight is size multiplied by depth */
67    if !morph.enabled || morph.morph_count == 0 {
68        return vec![];
69    }
70    let w = morph.global_size * morph.depth;
71    vec![w; morph.morph_count]
72}
73
74/// Enable or disable.
75pub fn psm_set_enabled(morph: &mut PoreSizeMorph, enabled: bool) {
76    morph.enabled = enabled;
77}
78
79/// Return zone override count.
80pub fn psm_zone_count(morph: &PoreSizeMorph) -> usize {
81    morph.zone_overrides.len()
82}
83
84/// Serialize to JSON-like string.
85pub fn psm_to_json(morph: &PoreSizeMorph) -> String {
86    format!(
87        r#"{{"global_size":{},"depth":{},"zones":{},"morph_count":{},"enabled":{}}}"#,
88        morph.global_size,
89        morph.depth,
90        morph.zone_overrides.len(),
91        morph.morph_count,
92        morph.enabled
93    )
94}
95
96#[cfg(test)]
97mod tests {
98    use super::*;
99
100    #[test]
101    fn test_default_size() {
102        let m = new_pore_size_morph(4);
103        assert!((m.global_size - 0.3).abs() < 1e-6 /* default size must be 0.3 */);
104    }
105
106    #[test]
107    fn test_set_size_clamps() {
108        let mut m = new_pore_size_morph(4);
109        psm_set_size(&mut m, 2.0);
110        assert!((m.global_size - 1.0).abs() < 1e-6 /* size clamped to 1.0 */);
111    }
112
113    #[test]
114    fn test_zone_added() {
115        let mut m = new_pore_size_morph(4);
116        psm_set_zone(&mut m, PoreZone::TZone, 0.7);
117        assert_eq!(psm_zone_count(&m), 1 /* one zone override added */);
118    }
119
120    #[test]
121    fn test_depth_clamped() {
122        let mut m = new_pore_size_morph(4);
123        psm_set_depth(&mut m, -1.0);
124        assert!((m.depth).abs() < 1e-6 /* depth clamped to 0.0 */);
125    }
126
127    #[test]
128    fn test_evaluate_length() {
129        let m = new_pore_size_morph(5);
130        assert_eq!(
131            psm_evaluate(&m).len(),
132            5 /* output must match morph_count */
133        );
134    }
135
136    #[test]
137    fn test_evaluate_disabled() {
138        let mut m = new_pore_size_morph(4);
139        psm_set_enabled(&mut m, false);
140        assert!(psm_evaluate(&m).is_empty() /* disabled must return empty */);
141    }
142
143    #[test]
144    fn test_evaluate_product() {
145        let mut m = new_pore_size_morph(2);
146        psm_set_size(&mut m, 0.5);
147        psm_set_depth(&mut m, 0.4);
148        let out = psm_evaluate(&m);
149        assert!((out[0] - 0.2).abs() < 1e-5 /* weight must be size * depth */);
150    }
151
152    #[test]
153    fn test_to_json_has_size() {
154        let m = new_pore_size_morph(4);
155        let j = psm_to_json(&m);
156        assert!(j.contains("\"global_size\"") /* JSON must have global_size */);
157    }
158
159    #[test]
160    fn test_enabled_default() {
161        let m = new_pore_size_morph(4);
162        assert!(m.enabled /* must be enabled by default */);
163    }
164
165    #[test]
166    fn test_zone_update_not_duplicate() {
167        let mut m = new_pore_size_morph(4);
168        psm_set_zone(&mut m, PoreZone::Nose, 0.3);
169        psm_set_zone(&mut m, PoreZone::Nose, 0.7);
170        assert_eq!(
171            psm_zone_count(&m),
172            1 /* same zone must update not duplicate */
173        );
174    }
175}