Skip to main content

oxihuman_morph/
morph_layer.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3
4#![allow(dead_code)]
5
6use std::collections::HashMap;
7
8/// How a layer blends with layers below it
9#[derive(Clone, Debug, PartialEq)]
10pub enum LayerBlend {
11    /// Override: this layer completely replaces weights below
12    Override,
13    /// Additive: add this layer's weights on top
14    Additive,
15    /// Multiply: multiply this layer's weights with those below
16    Multiply,
17    /// Screen: result = 1 - (1 - a) * (1 - b)
18    Screen,
19    /// Lerp: blend between base and this layer by layer opacity
20    Normal,
21}
22
23/// A single morph layer
24pub struct MorphLayer {
25    pub name: String,
26    pub blend_mode: LayerBlend,
27    pub opacity: f32,
28    pub enabled: bool,
29    /// Per-morph weights in this layer
30    pub weights: HashMap<String, f32>,
31    /// Optional per-morph mask (vertex group influence)
32    pub mask: Option<Vec<f32>>,
33}
34
35impl MorphLayer {
36    pub fn new(name: impl Into<String>) -> Self {
37        Self {
38            name: name.into(),
39            blend_mode: LayerBlend::Normal,
40            opacity: 1.0,
41            enabled: true,
42            weights: HashMap::new(),
43            mask: None,
44        }
45    }
46
47    pub fn with_blend(mut self, blend: LayerBlend) -> Self {
48        self.blend_mode = blend;
49        self
50    }
51
52    pub fn with_opacity(mut self, opacity: f32) -> Self {
53        self.opacity = opacity.clamp(0.0, 1.0);
54        self
55    }
56
57    pub fn set_weight(&mut self, morph: impl Into<String>, weight: f32) {
58        self.weights.insert(morph.into(), weight);
59    }
60
61    pub fn get_weight(&self, morph: &str) -> f32 {
62        self.weights.get(morph).copied().unwrap_or(0.0)
63    }
64
65    pub fn morph_names(&self) -> Vec<&str> {
66        self.weights.keys().map(|s| s.as_str()).collect()
67    }
68
69    /// Returns true if this layer is enabled and has non-zero opacity
70    pub fn is_active(&self) -> bool {
71        self.enabled && self.opacity > 0.0
72    }
73}
74
75/// Ordered stack of morph layers
76pub struct MorphLayerStack {
77    layers: Vec<MorphLayer>,
78}
79
80impl MorphLayerStack {
81    pub fn new() -> Self {
82        Self { layers: Vec::new() }
83    }
84
85    pub fn push(&mut self, layer: MorphLayer) {
86        self.layers.push(layer);
87    }
88
89    pub fn pop(&mut self) -> Option<MorphLayer> {
90        self.layers.pop()
91    }
92
93    pub fn insert(&mut self, index: usize, layer: MorphLayer) {
94        self.layers.insert(index, layer);
95    }
96
97    pub fn remove(&mut self, index: usize) -> MorphLayer {
98        self.layers.remove(index)
99    }
100
101    pub fn move_up(&mut self, index: usize) {
102        if index > 0 {
103            self.layers.swap(index, index - 1);
104        }
105    }
106
107    pub fn move_down(&mut self, index: usize) {
108        if index + 1 < self.layers.len() {
109            self.layers.swap(index, index + 1);
110        }
111    }
112
113    pub fn layer_count(&self) -> usize {
114        self.layers.len()
115    }
116
117    pub fn get(&self, index: usize) -> Option<&MorphLayer> {
118        self.layers.get(index)
119    }
120
121    pub fn get_mut(&mut self, index: usize) -> Option<&mut MorphLayer> {
122        self.layers.get_mut(index)
123    }
124
125    pub fn get_by_name(&self, name: &str) -> Option<&MorphLayer> {
126        self.layers.iter().find(|l| l.name == name)
127    }
128
129    pub fn get_by_name_mut(&mut self, name: &str) -> Option<&mut MorphLayer> {
130        self.layers.iter_mut().find(|l| l.name == name)
131    }
132
133    /// Evaluate all layers bottom-to-top, return composite morph weights
134    pub fn evaluate(&self) -> HashMap<String, f32> {
135        let mut base: HashMap<String, f32> = HashMap::new();
136        for layer in &self.layers {
137            if !layer.is_active() {
138                continue;
139            }
140            base = blend_layer(&base, &layer.weights, &layer.blend_mode, layer.opacity);
141        }
142        base
143    }
144
145    /// All unique morph names across all layers
146    pub fn all_morphs(&self) -> Vec<String> {
147        let mut names: std::collections::HashSet<String> = std::collections::HashSet::new();
148        for layer in &self.layers {
149            for key in layer.weights.keys() {
150                names.insert(key.clone());
151            }
152        }
153        let mut result: Vec<String> = names.into_iter().collect();
154        result.sort();
155        result
156    }
157}
158
159impl Default for MorphLayerStack {
160    fn default() -> Self {
161        Self::new()
162    }
163}
164
165/// Blend two morph weight maps using the given mode and opacity
166pub fn blend_layer(
167    base: &HashMap<String, f32>,
168    layer: &HashMap<String, f32>,
169    mode: &LayerBlend,
170    opacity: f32,
171) -> HashMap<String, f32> {
172    // Collect all keys from both maps
173    let mut all_keys: std::collections::HashSet<&str> = std::collections::HashSet::new();
174    for key in base.keys() {
175        all_keys.insert(key.as_str());
176    }
177    for key in layer.keys() {
178        all_keys.insert(key.as_str());
179    }
180
181    let mut result = HashMap::new();
182    for key in all_keys {
183        let a = base.get(key).copied().unwrap_or(0.0);
184        let b_raw = layer.get(key).copied().unwrap_or(0.0);
185        let b = b_raw * opacity;
186
187        let blended = match mode {
188            LayerBlend::Override => b,
189            LayerBlend::Additive => a + b,
190            LayerBlend::Multiply => a * b,
191            LayerBlend::Screen => 1.0 - (1.0 - a) * (1.0 - b),
192            LayerBlend::Normal => a * (1.0 - opacity) + b,
193        };
194
195        result.insert(key.to_string(), blended.clamp(0.0, 1.0));
196    }
197    result
198}
199
200#[cfg(test)]
201mod tests {
202    use super::*;
203
204    fn approx_eq(a: f32, b: f32) -> bool {
205        (a - b).abs() < 1e-5
206    }
207
208    #[test]
209    fn test_layer_new() {
210        let layer = MorphLayer::new("base");
211        assert_eq!(layer.name, "base");
212        assert_eq!(layer.blend_mode, LayerBlend::Normal);
213        assert_eq!(layer.opacity, 1.0);
214        assert!(layer.enabled);
215        assert!(layer.weights.is_empty());
216        assert!(layer.mask.is_none());
217    }
218
219    #[test]
220    fn test_layer_set_get_weight() {
221        let mut layer = MorphLayer::new("test");
222        layer.set_weight("smile", 0.75);
223        layer.set_weight("blink", 0.5);
224        assert!(approx_eq(layer.get_weight("smile"), 0.75));
225        assert!(approx_eq(layer.get_weight("blink"), 0.5));
226        assert!(approx_eq(layer.get_weight("missing"), 0.0));
227    }
228
229    #[test]
230    fn test_layer_is_active() {
231        let layer = MorphLayer::new("active");
232        assert!(layer.is_active());
233
234        let disabled = MorphLayer::new("disabled");
235        let mut disabled = disabled;
236        disabled.enabled = false;
237        assert!(!disabled.is_active());
238
239        let zero_opacity = MorphLayer::new("zero").with_opacity(0.0);
240        assert!(!zero_opacity.is_active());
241
242        let small_opacity = MorphLayer::new("small").with_opacity(0.001);
243        assert!(small_opacity.is_active());
244    }
245
246    #[test]
247    fn test_blend_override() {
248        let mut base = HashMap::new();
249        base.insert("smile".to_string(), 0.8);
250        let mut layer = HashMap::new();
251        layer.insert("smile".to_string(), 0.3);
252
253        let result = blend_layer(&base, &layer, &LayerBlend::Override, 1.0);
254        // Override: result = b = layer * opacity = 0.3 * 1.0 = 0.3
255        assert!(approx_eq(result["smile"], 0.3));
256    }
257
258    #[test]
259    fn test_blend_additive() {
260        let mut base = HashMap::new();
261        base.insert("smile".to_string(), 0.5);
262        let mut layer = HashMap::new();
263        layer.insert("smile".to_string(), 0.3);
264
265        let result = blend_layer(&base, &layer, &LayerBlend::Additive, 1.0);
266        // Additive: a + b = 0.5 + 0.3 = 0.8
267        assert!(approx_eq(result["smile"], 0.8));
268    }
269
270    #[test]
271    fn test_blend_multiply() {
272        let mut base = HashMap::new();
273        base.insert("smile".to_string(), 0.5);
274        let mut layer = HashMap::new();
275        layer.insert("smile".to_string(), 0.6);
276
277        let result = blend_layer(&base, &layer, &LayerBlend::Multiply, 1.0);
278        // Multiply: a * b = 0.5 * 0.6 = 0.3
279        assert!(approx_eq(result["smile"], 0.3));
280    }
281
282    #[test]
283    fn test_blend_screen() {
284        let mut base = HashMap::new();
285        base.insert("smile".to_string(), 0.5);
286        let mut layer = HashMap::new();
287        layer.insert("smile".to_string(), 0.5);
288
289        let result = blend_layer(&base, &layer, &LayerBlend::Screen, 1.0);
290        // Screen: 1 - (1 - 0.5) * (1 - 0.5) = 1 - 0.25 = 0.75
291        assert!(approx_eq(result["smile"], 0.75));
292    }
293
294    #[test]
295    fn test_blend_normal() {
296        let mut base = HashMap::new();
297        base.insert("smile".to_string(), 0.0);
298        let mut layer = HashMap::new();
299        layer.insert("smile".to_string(), 1.0);
300
301        let result = blend_layer(&base, &layer, &LayerBlend::Normal, 0.5);
302        // Normal: a * (1 - opacity) + b * opacity = 0.0 * 0.5 + (1.0 * 0.5) * 0.5 = 0.25
303        // b = b_raw * opacity = 1.0 * 0.5 = 0.5; result = 0.0 * 0.5 + 0.5 = 0.5 * 0.5 = 0.25 ...
304        // Actually: a*(1-opacity) + b = 0.0*0.5 + 0.5 = 0.5
305        assert!(approx_eq(result["smile"], 0.5));
306    }
307
308    #[test]
309    fn test_blend_opacity_zero() {
310        let mut base = HashMap::new();
311        base.insert("smile".to_string(), 0.8);
312        let mut layer = HashMap::new();
313        layer.insert("smile".to_string(), 0.3);
314
315        // With opacity=0, all modes: b = b_raw * 0 = 0
316        let result_add = blend_layer(&base, &layer, &LayerBlend::Additive, 0.0);
317        // Additive: a + b = 0.8 + 0.0 = 0.8
318        assert!(approx_eq(result_add["smile"], 0.8));
319
320        let result_override = blend_layer(&base, &layer, &LayerBlend::Override, 0.0);
321        // Override: b = 0.0
322        assert!(approx_eq(result_override["smile"], 0.0));
323    }
324
325    #[test]
326    fn test_stack_push_pop() {
327        let mut stack = MorphLayerStack::new();
328        assert_eq!(stack.layer_count(), 0);
329
330        stack.push(MorphLayer::new("layer1"));
331        stack.push(MorphLayer::new("layer2"));
332        assert_eq!(stack.layer_count(), 2);
333
334        let popped = stack.pop().expect("should succeed");
335        assert_eq!(popped.name, "layer2");
336        assert_eq!(stack.layer_count(), 1);
337    }
338
339    #[test]
340    fn test_stack_evaluate_empty() {
341        let stack = MorphLayerStack::new();
342        let result = stack.evaluate();
343        assert!(result.is_empty());
344    }
345
346    #[test]
347    fn test_stack_evaluate_single_layer() {
348        let mut stack = MorphLayerStack::new();
349        let mut layer = MorphLayer::new("base").with_blend(LayerBlend::Override);
350        layer.set_weight("smile", 0.6);
351        layer.set_weight("blink", 0.4);
352        stack.push(layer);
353
354        let result = stack.evaluate();
355        // Override with opacity 1.0: b = weight * 1.0
356        assert!(approx_eq(result["smile"], 0.6));
357        assert!(approx_eq(result["blink"], 0.4));
358    }
359
360    #[test]
361    fn test_stack_evaluate_two_layers() {
362        let mut stack = MorphLayerStack::new();
363
364        // Bottom layer: Override mode sets base
365        let mut base_layer = MorphLayer::new("base").with_blend(LayerBlend::Override);
366        base_layer.set_weight("smile", 0.5);
367        stack.push(base_layer);
368
369        // Top layer: Additive mode adds on top
370        let mut additive_layer = MorphLayer::new("additive").with_blend(LayerBlend::Additive);
371        additive_layer.set_weight("smile", 0.3);
372        stack.push(additive_layer);
373
374        let result = stack.evaluate();
375        // After override: smile=0.5; after additive: 0.5 + 0.3 = 0.8
376        assert!(approx_eq(result["smile"], 0.8));
377    }
378
379    #[test]
380    fn test_stack_move_up_down() {
381        let mut stack = MorphLayerStack::new();
382        stack.push(MorphLayer::new("a"));
383        stack.push(MorphLayer::new("b"));
384        stack.push(MorphLayer::new("c"));
385
386        // Move index 2 ("c") up to index 1
387        stack.move_up(2);
388        assert_eq!(stack.get(1).expect("should succeed").name, "c");
389        assert_eq!(stack.get(2).expect("should succeed").name, "b");
390
391        // Move index 1 ("c") down to index 2
392        stack.move_down(1);
393        assert_eq!(stack.get(1).expect("should succeed").name, "b");
394        assert_eq!(stack.get(2).expect("should succeed").name, "c");
395
396        // Move up at index 0 is a no-op
397        stack.move_up(0);
398        assert_eq!(stack.get(0).expect("should succeed").name, "a");
399
400        // Move down at last index is a no-op
401        stack.move_down(2);
402        assert_eq!(stack.get(2).expect("should succeed").name, "c");
403    }
404
405    #[test]
406    fn test_stack_all_morphs() {
407        let mut stack = MorphLayerStack::new();
408
409        let mut layer1 = MorphLayer::new("l1");
410        layer1.set_weight("smile", 0.5);
411        layer1.set_weight("blink", 0.3);
412
413        let mut layer2 = MorphLayer::new("l2");
414        layer2.set_weight("blink", 0.7);
415        layer2.set_weight("frown", 0.2);
416
417        stack.push(layer1);
418        stack.push(layer2);
419
420        let mut morphs = stack.all_morphs();
421        morphs.sort();
422        assert_eq!(morphs, vec!["blink", "frown", "smile"]);
423    }
424}