Skip to main content

oxihuman_morph/
cheek_rise_control.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! Cheek rise control — zygomaticus major elevation under the eyes.
6
7/// Configuration for cheek rise.
8#[allow(dead_code)]
9#[derive(Debug, Clone)]
10pub struct CheekRiseConfig {
11    pub max_rise: f32,
12}
13
14/// Side selector.
15#[allow(dead_code)]
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum CheekRiseSide {
18    Left,
19    Right,
20}
21
22/// Runtime state.
23#[allow(dead_code)]
24#[derive(Debug, Clone)]
25pub struct CheekRiseState {
26    pub left_rise: f32,
27    pub right_rise: f32,
28}
29
30#[allow(dead_code)]
31pub fn default_cheek_rise_config() -> CheekRiseConfig {
32    CheekRiseConfig { max_rise: 1.0 }
33}
34
35#[allow(dead_code)]
36pub fn new_cheek_rise_state() -> CheekRiseState {
37    CheekRiseState {
38        left_rise: 0.0,
39        right_rise: 0.0,
40    }
41}
42
43#[allow(dead_code)]
44pub fn cr_set_rise(state: &mut CheekRiseState, cfg: &CheekRiseConfig, side: CheekRiseSide, v: f32) {
45    let clamped = v.clamp(0.0, cfg.max_rise);
46    match side {
47        CheekRiseSide::Left => state.left_rise = clamped,
48        CheekRiseSide::Right => state.right_rise = clamped,
49    }
50}
51
52#[allow(dead_code)]
53pub fn cr_set_both(state: &mut CheekRiseState, cfg: &CheekRiseConfig, v: f32) {
54    let clamped = v.clamp(0.0, cfg.max_rise);
55    state.left_rise = clamped;
56    state.right_rise = clamped;
57}
58
59#[allow(dead_code)]
60pub fn cr_reset(state: &mut CheekRiseState) {
61    *state = new_cheek_rise_state();
62}
63
64#[allow(dead_code)]
65pub fn cr_is_neutral(state: &CheekRiseState) -> bool {
66    state.left_rise.abs() < 1e-6 && state.right_rise.abs() < 1e-6
67}
68
69#[allow(dead_code)]
70pub fn cr_average(state: &CheekRiseState) -> f32 {
71    (state.left_rise + state.right_rise) * 0.5
72}
73
74#[allow(dead_code)]
75pub fn cr_symmetry(state: &CheekRiseState) -> f32 {
76    (state.left_rise - state.right_rise).abs()
77}
78
79#[allow(dead_code)]
80pub fn cr_blend(a: &CheekRiseState, b: &CheekRiseState, t: f32) -> CheekRiseState {
81    let t = t.clamp(0.0, 1.0);
82    CheekRiseState {
83        left_rise: a.left_rise + (b.left_rise - a.left_rise) * t,
84        right_rise: a.right_rise + (b.right_rise - a.right_rise) * t,
85    }
86}
87
88#[allow(dead_code)]
89pub fn cr_to_weights(state: &CheekRiseState) -> Vec<(String, f32)> {
90    vec![
91        ("cheek_rise_l".to_string(), state.left_rise),
92        ("cheek_rise_r".to_string(), state.right_rise),
93    ]
94}
95
96#[allow(dead_code)]
97pub fn cr_to_json(state: &CheekRiseState) -> String {
98    format!(
99        r#"{{"left_rise":{:.4},"right_rise":{:.4}}}"#,
100        state.left_rise, state.right_rise
101    )
102}
103
104#[cfg(test)]
105mod tests {
106    use super::*;
107
108    #[test]
109    fn default_config() {
110        let cfg = default_cheek_rise_config();
111        assert!((cfg.max_rise - 1.0).abs() < 1e-6);
112    }
113
114    #[test]
115    fn new_state_neutral() {
116        let s = new_cheek_rise_state();
117        assert!(cr_is_neutral(&s));
118    }
119
120    #[test]
121    fn set_rise_left() {
122        let cfg = default_cheek_rise_config();
123        let mut s = new_cheek_rise_state();
124        cr_set_rise(&mut s, &cfg, CheekRiseSide::Left, 0.7);
125        assert!((s.left_rise - 0.7).abs() < 1e-6);
126    }
127
128    #[test]
129    fn set_rise_clamps() {
130        let cfg = default_cheek_rise_config();
131        let mut s = new_cheek_rise_state();
132        cr_set_rise(&mut s, &cfg, CheekRiseSide::Right, 2.0);
133        assert!((s.right_rise - 1.0).abs() < 1e-6);
134    }
135
136    #[test]
137    fn set_both_symmetric() {
138        let cfg = default_cheek_rise_config();
139        let mut s = new_cheek_rise_state();
140        cr_set_both(&mut s, &cfg, 0.5);
141        assert!(cr_symmetry(&s) < 1e-6);
142    }
143
144    #[test]
145    fn average_value() {
146        let cfg = default_cheek_rise_config();
147        let mut s = new_cheek_rise_state();
148        cr_set_rise(&mut s, &cfg, CheekRiseSide::Left, 0.2);
149        cr_set_rise(&mut s, &cfg, CheekRiseSide::Right, 0.8);
150        assert!((cr_average(&s) - 0.5).abs() < 1e-6);
151    }
152
153    #[test]
154    fn reset_clears() {
155        let cfg = default_cheek_rise_config();
156        let mut s = new_cheek_rise_state();
157        cr_set_both(&mut s, &cfg, 0.9);
158        cr_reset(&mut s);
159        assert!(cr_is_neutral(&s));
160    }
161
162    #[test]
163    fn blend_midpoint() {
164        let a = new_cheek_rise_state();
165        let cfg = default_cheek_rise_config();
166        let mut b = new_cheek_rise_state();
167        cr_set_both(&mut b, &cfg, 1.0);
168        let m = cr_blend(&a, &b, 0.5);
169        assert!((m.left_rise - 0.5).abs() < 1e-6);
170    }
171
172    #[test]
173    fn to_weights_count() {
174        let s = new_cheek_rise_state();
175        assert_eq!(cr_to_weights(&s).len(), 2);
176    }
177
178    #[test]
179    fn to_json_contains_fields() {
180        let s = new_cheek_rise_state();
181        let j = cr_to_json(&s);
182        assert!(j.contains("left_rise"));
183    }
184}