Skip to main content

oxihuman_morph/
lip_volume_morph.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! Lip volume/plumpness morph stub.
6
7/// Lip area to control.
8#[derive(Debug, Clone, Copy, PartialEq)]
9pub enum LipArea {
10    Upper,
11    Lower,
12    Both,
13    Cupid,
14    Corners,
15}
16
17/// Lip volume morph controller.
18#[derive(Debug, Clone)]
19pub struct LipVolumeMorph {
20    pub area: LipArea,
21    pub volume: f32,
22    pub projection: f32,
23    pub definition: f32,
24    pub morph_count: usize,
25    pub enabled: bool,
26}
27
28impl LipVolumeMorph {
29    pub fn new(morph_count: usize) -> Self {
30        LipVolumeMorph {
31            area: LipArea::Both,
32            volume: 0.5,
33            projection: 0.5,
34            definition: 0.5,
35            morph_count,
36            enabled: true,
37        }
38    }
39}
40
41/// Create a new lip volume morph controller.
42pub fn new_lip_volume_morph(morph_count: usize) -> LipVolumeMorph {
43    LipVolumeMorph::new(morph_count)
44}
45
46/// Set target lip area.
47pub fn lvm_set_area(morph: &mut LipVolumeMorph, area: LipArea) {
48    morph.area = area;
49}
50
51/// Set lip volume.
52pub fn lvm_set_volume(morph: &mut LipVolumeMorph, volume: f32) {
53    morph.volume = volume.clamp(0.0, 1.0);
54}
55
56/// Set lip projection (forward push).
57pub fn lvm_set_projection(morph: &mut LipVolumeMorph, projection: f32) {
58    morph.projection = projection.clamp(0.0, 1.0);
59}
60
61/// Set lip edge definition.
62pub fn lvm_set_definition(morph: &mut LipVolumeMorph, definition: f32) {
63    morph.definition = definition.clamp(0.0, 1.0);
64}
65
66/// Evaluate morph weights (stub: combined volume and projection).
67pub fn lvm_evaluate(morph: &LipVolumeMorph) -> Vec<f32> {
68    /* Stub: weight from volume and projection average */
69    if !morph.enabled || morph.morph_count == 0 {
70        return vec![];
71    }
72    let w = (morph.volume + morph.projection) * 0.5;
73    vec![w.clamp(0.0, 1.0); morph.morph_count]
74}
75
76/// Enable or disable.
77pub fn lvm_set_enabled(morph: &mut LipVolumeMorph, enabled: bool) {
78    morph.enabled = enabled;
79}
80
81/// Serialize to JSON-like string.
82pub fn lvm_to_json(morph: &LipVolumeMorph) -> String {
83    let area = match morph.area {
84        LipArea::Upper => "upper",
85        LipArea::Lower => "lower",
86        LipArea::Both => "both",
87        LipArea::Cupid => "cupid",
88        LipArea::Corners => "corners",
89    };
90    format!(
91        r#"{{"area":"{}","volume":{},"projection":{},"definition":{},"enabled":{}}}"#,
92        area, morph.volume, morph.projection, morph.definition, morph.enabled
93    )
94}
95
96#[cfg(test)]
97mod tests {
98    use super::*;
99
100    #[test]
101    fn test_default_area() {
102        let m = new_lip_volume_morph(4);
103        assert_eq!(m.area, LipArea::Both /* default area must be Both */);
104    }
105
106    #[test]
107    fn test_set_area() {
108        let mut m = new_lip_volume_morph(4);
109        lvm_set_area(&mut m, LipArea::Upper);
110        assert_eq!(m.area, LipArea::Upper /* area must be set */);
111    }
112
113    #[test]
114    fn test_volume_clamped() {
115        let mut m = new_lip_volume_morph(4);
116        lvm_set_volume(&mut m, 2.0);
117        assert!((m.volume - 1.0).abs() < 1e-6 /* volume clamped to 1.0 */);
118    }
119
120    #[test]
121    fn test_projection_clamped() {
122        let mut m = new_lip_volume_morph(4);
123        lvm_set_projection(&mut m, -1.0);
124        assert!((m.projection).abs() < 1e-6 /* projection clamped to 0.0 */);
125    }
126
127    #[test]
128    fn test_definition_clamped() {
129        let mut m = new_lip_volume_morph(4);
130        lvm_set_definition(&mut m, 1.5);
131        assert!((m.definition - 1.0).abs() < 1e-6 /* definition clamped to 1.0 */);
132    }
133
134    #[test]
135    fn test_evaluate_length() {
136        let m = new_lip_volume_morph(6);
137        assert_eq!(
138            lvm_evaluate(&m).len(),
139            6 /* output must match morph_count */
140        );
141    }
142
143    #[test]
144    fn test_evaluate_disabled() {
145        let mut m = new_lip_volume_morph(4);
146        lvm_set_enabled(&mut m, false);
147        assert!(lvm_evaluate(&m).is_empty() /* disabled must return empty */);
148    }
149
150    #[test]
151    fn test_evaluate_avg() {
152        let mut m = new_lip_volume_morph(2);
153        lvm_set_volume(&mut m, 0.6);
154        lvm_set_projection(&mut m, 0.4);
155        let out = lvm_evaluate(&m);
156        assert!((out[0] - 0.5).abs() < 1e-5 /* weight must be average of volume and projection */);
157    }
158
159    #[test]
160    fn test_to_json_has_area() {
161        let m = new_lip_volume_morph(4);
162        let j = lvm_to_json(&m);
163        assert!(j.contains("\"area\"") /* JSON must have area */);
164    }
165
166    #[test]
167    fn test_enabled_default() {
168        let m = new_lip_volume_morph(4);
169        assert!(m.enabled /* must be enabled by default */);
170    }
171}