Skip to main content

oxihuman_morph/
chin_shape_control.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3
4#![allow(dead_code)]
5
6//! Chin shape control: adjusts chin projection, width and vertical length.
7
8use std::f32::consts::FRAC_PI_2;
9
10#[allow(dead_code)]
11#[derive(Debug, Clone)]
12pub struct ChinShapeConfig {
13    pub min_val: f32,
14    pub max_val: f32,
15}
16
17#[allow(dead_code)]
18#[derive(Debug, Clone)]
19pub struct ChinShapeState {
20    pub projection: f32,
21    pub width: f32,
22    pub vertical: f32,
23    pub cleft_depth: f32,
24}
25
26#[allow(dead_code)]
27pub fn default_chin_shape_config() -> ChinShapeConfig {
28    ChinShapeConfig {
29        min_val: 0.0,
30        max_val: 1.0,
31    }
32}
33
34#[allow(dead_code)]
35pub fn new_chin_shape_state() -> ChinShapeState {
36    ChinShapeState {
37        projection: 0.5,
38        width: 0.5,
39        vertical: 0.5,
40        cleft_depth: 0.0,
41    }
42}
43
44#[allow(dead_code)]
45pub fn cs_set_projection(state: &mut ChinShapeState, cfg: &ChinShapeConfig, v: f32) {
46    state.projection = v.clamp(cfg.min_val, cfg.max_val);
47}
48
49#[allow(dead_code)]
50pub fn cs_set_width(state: &mut ChinShapeState, cfg: &ChinShapeConfig, v: f32) {
51    state.width = v.clamp(cfg.min_val, cfg.max_val);
52}
53
54#[allow(dead_code)]
55pub fn cs_set_vertical(state: &mut ChinShapeState, cfg: &ChinShapeConfig, v: f32) {
56    state.vertical = v.clamp(cfg.min_val, cfg.max_val);
57}
58
59#[allow(dead_code)]
60pub fn cs_set_cleft(state: &mut ChinShapeState, v: f32) {
61    state.cleft_depth = v.clamp(0.0, 1.0);
62}
63
64#[allow(dead_code)]
65pub fn cs_reset(state: &mut ChinShapeState) {
66    *state = new_chin_shape_state();
67}
68
69#[allow(dead_code)]
70pub fn cs_profile_angle(state: &ChinShapeState) -> f32 {
71    state.projection * FRAC_PI_2
72}
73
74#[allow(dead_code)]
75pub fn cs_to_weights(state: &ChinShapeState) -> Vec<(String, f32)> {
76    vec![
77        ("chin_projection".to_string(), state.projection),
78        ("chin_width".to_string(), state.width),
79        ("chin_vertical".to_string(), state.vertical),
80        ("chin_cleft_depth".to_string(), state.cleft_depth),
81    ]
82}
83
84#[allow(dead_code)]
85pub fn cs_to_json(state: &ChinShapeState) -> String {
86    format!(
87        r#"{{"projection":{:.4},"width":{:.4},"vertical":{:.4},"cleft_depth":{:.4}}}"#,
88        state.projection, state.width, state.vertical, state.cleft_depth
89    )
90}
91
92#[allow(dead_code)]
93pub fn cs_blend(a: &ChinShapeState, b: &ChinShapeState, t: f32) -> ChinShapeState {
94    let t = t.clamp(0.0, 1.0);
95    ChinShapeState {
96        projection: a.projection + (b.projection - a.projection) * t,
97        width: a.width + (b.width - a.width) * t,
98        vertical: a.vertical + (b.vertical - a.vertical) * t,
99        cleft_depth: a.cleft_depth + (b.cleft_depth - a.cleft_depth) * t,
100    }
101}
102
103#[cfg(test)]
104mod tests {
105    use super::*;
106
107    #[test]
108    fn test_default_config() {
109        let cfg = default_chin_shape_config();
110        assert!(cfg.min_val.abs() < 1e-6);
111    }
112
113    #[test]
114    fn test_new_state() {
115        let s = new_chin_shape_state();
116        assert!((s.projection - 0.5).abs() < 1e-6);
117    }
118
119    #[test]
120    fn test_set_projection_clamps() {
121        let cfg = default_chin_shape_config();
122        let mut s = new_chin_shape_state();
123        cs_set_projection(&mut s, &cfg, 5.0);
124        assert!((s.projection - 1.0).abs() < 1e-6);
125    }
126
127    #[test]
128    fn test_set_width() {
129        let cfg = default_chin_shape_config();
130        let mut s = new_chin_shape_state();
131        cs_set_width(&mut s, &cfg, 0.7);
132        assert!((s.width - 0.7).abs() < 1e-6);
133    }
134
135    #[test]
136    fn test_set_vertical() {
137        let cfg = default_chin_shape_config();
138        let mut s = new_chin_shape_state();
139        cs_set_vertical(&mut s, &cfg, 0.3);
140        assert!((s.vertical - 0.3).abs() < 1e-6);
141    }
142
143    #[test]
144    fn test_set_cleft() {
145        let mut s = new_chin_shape_state();
146        cs_set_cleft(&mut s, 0.6);
147        assert!((s.cleft_depth - 0.6).abs() < 1e-6);
148    }
149
150    #[test]
151    fn test_reset() {
152        let cfg = default_chin_shape_config();
153        let mut s = new_chin_shape_state();
154        cs_set_projection(&mut s, &cfg, 0.9);
155        cs_reset(&mut s);
156        assert!((s.projection - 0.5).abs() < 1e-6);
157    }
158
159    #[test]
160    fn test_profile_angle() {
161        let s = new_chin_shape_state();
162        let a = cs_profile_angle(&s);
163        assert!((a - 0.5 * FRAC_PI_2).abs() < 1e-6);
164    }
165
166    #[test]
167    fn test_to_weights() {
168        let s = new_chin_shape_state();
169        assert_eq!(cs_to_weights(&s).len(), 4);
170    }
171
172    #[test]
173    fn test_blend() {
174        let a = new_chin_shape_state();
175        let mut b = new_chin_shape_state();
176        b.projection = 1.0;
177        let mid = cs_blend(&a, &b, 0.5);
178        assert!((mid.projection - 0.75).abs() < 1e-6);
179    }
180}