oxihuman_morph/
chin_dimple_control.rs1#![allow(dead_code)]
4
5use std::f32::consts::FRAC_PI_4;
8
9#[allow(dead_code)]
11#[derive(Clone, Debug)]
12pub struct ChinDimpleState {
13 pub depth: f32,
15 pub width: f32,
17 pub vertical_offset: f32,
19}
20
21#[allow(dead_code)]
23#[derive(Clone, Debug)]
24pub struct ChinDimpleConfig {
25 pub max_depth: f32,
26 pub max_width: f32,
27}
28
29impl Default for ChinDimpleConfig {
30 fn default() -> Self {
31 Self {
32 max_depth: 1.0,
33 max_width: 2.0,
34 }
35 }
36}
37
38impl Default for ChinDimpleState {
39 fn default() -> Self {
40 Self {
41 depth: 0.0,
42 width: 1.0,
43 vertical_offset: 0.0,
44 }
45 }
46}
47
48#[allow(dead_code)]
49pub fn new_chin_dimple_state() -> ChinDimpleState {
50 ChinDimpleState::default()
51}
52
53#[allow(dead_code)]
54pub fn default_chin_dimple_config() -> ChinDimpleConfig {
55 ChinDimpleConfig::default()
56}
57
58#[allow(dead_code)]
59pub fn cd_set_depth(state: &mut ChinDimpleState, cfg: &ChinDimpleConfig, v: f32) {
60 state.depth = v.clamp(0.0, cfg.max_depth);
61}
62
63#[allow(dead_code)]
64pub fn cd_set_width(state: &mut ChinDimpleState, cfg: &ChinDimpleConfig, v: f32) {
65 state.width = v.clamp(0.1, cfg.max_width);
66}
67
68#[allow(dead_code)]
69pub fn cd_set_vertical_offset(state: &mut ChinDimpleState, v: f32) {
70 state.vertical_offset = v.clamp(-1.0, 1.0);
71}
72
73#[allow(dead_code)]
74pub fn cd_reset(state: &mut ChinDimpleState) {
75 *state = ChinDimpleState::default();
76}
77
78#[allow(dead_code)]
79pub fn cd_is_neutral(state: &ChinDimpleState) -> bool {
80 state.depth < 1e-4
81}
82
83#[allow(dead_code)]
84pub fn cd_blend(a: &ChinDimpleState, b: &ChinDimpleState, t: f32) -> ChinDimpleState {
85 let t = t.clamp(0.0, 1.0);
86 ChinDimpleState {
87 depth: a.depth + (b.depth - a.depth) * t,
88 width: a.width + (b.width - a.width) * t,
89 vertical_offset: a.vertical_offset + (b.vertical_offset - a.vertical_offset) * t,
90 }
91}
92
93#[allow(dead_code)]
95pub fn cd_area(state: &ChinDimpleState) -> f32 {
96 state.depth * state.width * FRAC_PI_4
98}
99
100#[allow(dead_code)]
101pub fn cd_to_weights(state: &ChinDimpleState) -> [f32; 3] {
102 [state.depth, state.width - 1.0, state.vertical_offset]
103}
104
105#[allow(dead_code)]
106pub fn cd_to_json(state: &ChinDimpleState) -> String {
107 format!(
108 "{{\"depth\":{:.4},\"width\":{:.4},\"v_offset\":{:.4}}}",
109 state.depth, state.width, state.vertical_offset
110 )
111}
112
113#[allow(dead_code)]
114pub fn cd_effective_depth(state: &ChinDimpleState) -> f32 {
115 state.depth * state.width.sqrt()
116}
117
118#[cfg(test)]
119mod tests {
120 use super::*;
121
122 #[test]
123 fn default_neutral() {
124 assert!(cd_is_neutral(&new_chin_dimple_state()));
125 }
126
127 #[test]
128 fn depth_clamp_max() {
129 let mut s = new_chin_dimple_state();
130 let cfg = default_chin_dimple_config();
131 cd_set_depth(&mut s, &cfg, 5.0);
132 assert!(s.depth <= cfg.max_depth);
133 }
134
135 #[test]
136 fn depth_not_negative() {
137 let mut s = new_chin_dimple_state();
138 let cfg = default_chin_dimple_config();
139 cd_set_depth(&mut s, &cfg, -1.0);
140 assert!(s.depth >= 0.0);
141 }
142
143 #[test]
144 fn width_min_clamp() {
145 let mut s = new_chin_dimple_state();
146 let cfg = default_chin_dimple_config();
147 cd_set_width(&mut s, &cfg, 0.0);
148 assert!(s.width >= 0.1);
149 }
150
151 #[test]
152 fn reset_neutral() {
153 let mut s = new_chin_dimple_state();
154 let cfg = default_chin_dimple_config();
155 cd_set_depth(&mut s, &cfg, 0.8);
156 cd_reset(&mut s);
157 assert!(cd_is_neutral(&s));
158 }
159
160 #[test]
161 fn blend_half() {
162 let cfg = default_chin_dimple_config();
163 let mut a = new_chin_dimple_state();
164 let mut b = new_chin_dimple_state();
165 cd_set_depth(&mut a, &cfg, 0.0);
166 cd_set_depth(&mut b, &cfg, 1.0);
167 let m = cd_blend(&a, &b, 0.5);
168 assert!((m.depth - 0.5).abs() < 1e-4);
169 }
170
171 #[test]
172 fn area_zero_when_no_depth() {
173 let s = new_chin_dimple_state();
174 assert!(cd_area(&s).abs() < 1e-5);
175 }
176
177 #[test]
178 fn weights_len() {
179 assert_eq!(cd_to_weights(&new_chin_dimple_state()).len(), 3);
180 }
181
182 #[test]
183 fn json_contains_depth() {
184 assert!(cd_to_json(&new_chin_dimple_state()).contains("depth"));
185 }
186
187 #[test]
188 fn effective_depth_positive() {
189 let mut s = new_chin_dimple_state();
190 let cfg = default_chin_dimple_config();
191 cd_set_depth(&mut s, &cfg, 0.5);
192 assert!(cd_effective_depth(&s) > 0.0);
193 }
194}