Skip to main content

oxihuman_morph/
ethnic_blend_morph.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! Ethnic feature blend morph stub.
6
7/// An ethnic feature set with associated morph weights.
8#[derive(Debug, Clone)]
9pub struct EthnicFeatureSet {
10    pub name: String,
11    pub morph_weights: Vec<f32>,
12}
13
14/// Ethnic blend morph controller.
15#[derive(Debug, Clone)]
16pub struct EthnicBlendMorph {
17    pub feature_sets: Vec<EthnicFeatureSet>,
18    pub blend_weights: Vec<f32>,
19    pub morph_count: usize,
20    pub enabled: bool,
21}
22
23impl EthnicBlendMorph {
24    pub fn new(morph_count: usize) -> Self {
25        EthnicBlendMorph {
26            feature_sets: Vec::new(),
27            blend_weights: Vec::new(),
28            morph_count,
29            enabled: true,
30        }
31    }
32}
33
34/// Create a new ethnic blend morph controller.
35pub fn new_ethnic_blend_morph(morph_count: usize) -> EthnicBlendMorph {
36    EthnicBlendMorph::new(morph_count)
37}
38
39/// Add an ethnic feature set.
40pub fn ebmrph_add_feature_set(morph: &mut EthnicBlendMorph, feature_set: EthnicFeatureSet) {
41    morph.blend_weights.push(0.0);
42    morph.feature_sets.push(feature_set);
43}
44
45/// Set the blend weight for a feature set.
46pub fn ebmrph_set_blend_weight(morph: &mut EthnicBlendMorph, idx: usize, weight: f32) {
47    if idx < morph.blend_weights.len() {
48        morph.blend_weights[idx] = weight.clamp(0.0, 1.0);
49    }
50}
51
52/// Evaluate blended morph weights (stub: zeroed).
53pub fn ebmrph_evaluate(morph: &EthnicBlendMorph) -> Vec<f32> {
54    /* Stub: returns zeroed output */
55    vec![0.0; morph.morph_count]
56}
57
58/// Return feature set count.
59pub fn ebmrph_feature_count(morph: &EthnicBlendMorph) -> usize {
60    morph.feature_sets.len()
61}
62
63/// Enable or disable.
64pub fn ebmrph_set_enabled(morph: &mut EthnicBlendMorph, enabled: bool) {
65    morph.enabled = enabled;
66}
67
68/// Serialize to JSON-like string.
69pub fn ebmrph_to_json(morph: &EthnicBlendMorph) -> String {
70    format!(
71        r#"{{"morph_count":{},"feature_sets":{},"enabled":{}}}"#,
72        morph.morph_count,
73        morph.feature_sets.len(),
74        morph.enabled
75    )
76}
77
78#[cfg(test)]
79mod tests {
80    use super::*;
81
82    #[test]
83    fn test_new_morph_count() {
84        let m = new_ethnic_blend_morph(16);
85        assert_eq!(m.morph_count, 16 /* morph count must match */,);
86    }
87
88    #[test]
89    fn test_no_features_initially() {
90        let m = new_ethnic_blend_morph(8);
91        assert_eq!(
92            ebmrph_feature_count(&m),
93            0, /* no feature sets initially */
94        );
95    }
96
97    #[test]
98    fn test_add_feature_set() {
99        let mut m = new_ethnic_blend_morph(8);
100        ebmrph_add_feature_set(
101            &mut m,
102            EthnicFeatureSet {
103                name: "set_a".into(),
104                morph_weights: vec![0.0; 8],
105            },
106        );
107        assert_eq!(
108            ebmrph_feature_count(&m),
109            1, /* one feature set after add */
110        );
111    }
112
113    #[test]
114    fn test_blend_weight_init_zero() {
115        let mut m = new_ethnic_blend_morph(4);
116        ebmrph_add_feature_set(
117            &mut m,
118            EthnicFeatureSet {
119                name: "x".into(),
120                morph_weights: vec![0.0; 4],
121            },
122        );
123        assert!((m.blend_weights[0]).abs() < 1e-6, /* initial blend weight must be 0 */);
124    }
125
126    #[test]
127    fn test_set_blend_weight() {
128        let mut m = new_ethnic_blend_morph(4);
129        ebmrph_add_feature_set(
130            &mut m,
131            EthnicFeatureSet {
132                name: "x".into(),
133                morph_weights: vec![0.0; 4],
134            },
135        );
136        ebmrph_set_blend_weight(&mut m, 0, 0.7);
137        assert!((m.blend_weights[0] - 0.7).abs() < 1e-5, /* weight must be set */);
138    }
139
140    #[test]
141    fn test_blend_weight_clamped() {
142        let mut m = new_ethnic_blend_morph(4);
143        ebmrph_add_feature_set(
144            &mut m,
145            EthnicFeatureSet {
146                name: "x".into(),
147                morph_weights: vec![0.0; 4],
148            },
149        );
150        ebmrph_set_blend_weight(&mut m, 0, 2.0);
151        assert!((m.blend_weights[0] - 1.0).abs() < 1e-6, /* weight clamped to 1.0 */);
152    }
153
154    #[test]
155    fn test_evaluate_output_length() {
156        let m = new_ethnic_blend_morph(10);
157        let out = ebmrph_evaluate(&m);
158        assert_eq!(
159            out.len(),
160            10, /* output length must match morph_count */
161        );
162    }
163
164    #[test]
165    fn test_set_enabled() {
166        let mut m = new_ethnic_blend_morph(4);
167        ebmrph_set_enabled(&mut m, false);
168        assert!(!m.enabled /* must be disabled */,);
169    }
170
171    #[test]
172    fn test_to_json_contains_morph_count() {
173        let m = new_ethnic_blend_morph(5);
174        let j = ebmrph_to_json(&m);
175        assert!(j.contains("\"morph_count\""), /* json must contain morph_count */);
176    }
177
178    #[test]
179    fn test_enabled_default() {
180        let m = new_ethnic_blend_morph(1);
181        assert!(m.enabled /* must be enabled by default */,);
182    }
183
184    #[test]
185    fn test_out_of_bounds_set_ignored() {
186        let mut m = new_ethnic_blend_morph(4);
187        ebmrph_set_blend_weight(&mut m, 99, 1.0);
188        assert_eq!(
189            ebmrph_feature_count(&m),
190            0, /* out of bounds set must be ignored */
191        );
192    }
193}