oxihuman_morph/
waist_control.rs1#![allow(dead_code)]
7
8use std::f32::consts::PI;
9
10#[allow(dead_code)]
11#[derive(Debug, Clone)]
12pub struct WaistConfig {
13 pub min_width: f32,
14 pub max_width: f32,
15}
16
17#[allow(dead_code)]
18#[derive(Debug, Clone)]
19pub struct WaistState {
20 pub width: f32,
21 pub depth: f32,
22 pub height: f32,
23}
24
25#[allow(dead_code)]
26pub fn default_waist_config() -> WaistConfig {
27 WaistConfig {
28 min_width: 0.2,
29 max_width: 1.0,
30 }
31}
32
33#[allow(dead_code)]
34pub fn new_waist_state() -> WaistState {
35 WaistState {
36 width: 0.5,
37 depth: 0.5,
38 height: 0.5,
39 }
40}
41
42#[allow(dead_code)]
43pub fn waist_set_width(state: &mut WaistState, cfg: &WaistConfig, value: f32) {
44 state.width = value.clamp(cfg.min_width, cfg.max_width);
45}
46
47#[allow(dead_code)]
48pub fn waist_set_depth(state: &mut WaistState, value: f32) {
49 state.depth = value.clamp(0.0, 1.0);
50}
51
52#[allow(dead_code)]
54pub fn waist_compute_circumference(state: &WaistState, scale_m: f32) -> f32 {
55 let a = state.width * scale_m;
56 let b = state.depth * scale_m;
57 let h = (a - b).powi(2) / (a + b).powi(2);
58 PI * (a + b) * (1.0 + (3.0 * h) / (10.0 + (4.0 - 3.0 * h).sqrt()))
59}
60
61#[allow(dead_code)]
62pub fn waist_reset(state: &mut WaistState) {
63 *state = new_waist_state();
64}
65
66#[allow(dead_code)]
67pub fn waist_to_weights(state: &WaistState) -> Vec<(String, f32)> {
68 vec![
69 ("waist_width".to_string(), state.width),
70 ("waist_depth".to_string(), state.depth),
71 ("waist_height".to_string(), state.height),
72 ]
73}
74
75#[allow(dead_code)]
76pub fn waist_to_json(state: &WaistState) -> String {
77 format!(
78 r#"{{"width":{:.4},"depth":{:.4},"height":{:.4}}}"#,
79 state.width, state.depth, state.height
80 )
81}
82
83#[allow(dead_code)]
84pub fn waist_clamp(state: &mut WaistState, cfg: &WaistConfig) {
85 state.width = state.width.clamp(cfg.min_width, cfg.max_width);
86 state.depth = state.depth.clamp(0.0, 1.0);
87 state.height = state.height.clamp(0.0, 1.0);
88}
89
90#[allow(dead_code)]
94#[derive(Debug, Clone)]
95pub struct WaistControl {
96 pub width: f32,
97 pub depth: f32,
98 pub height_offset: f32,
99}
100
101#[allow(dead_code)]
103pub fn default_waist_control() -> WaistControl {
104 WaistControl {
105 width: 0.5,
106 depth: 0.5,
107 height_offset: 0.0,
108 }
109}
110
111#[allow(dead_code)]
113pub fn apply_waist_control(weights: &mut [f32], wc: &WaistControl) {
114 if !weights.is_empty() {
115 weights[0] = wc.width;
116 }
117 if weights.len() > 1 {
118 weights[1] = wc.depth;
119 }
120 if weights.len() > 2 {
121 weights[2] = wc.height_offset;
122 }
123}
124
125#[allow(dead_code)]
127pub fn waist_control_blend(a: &WaistControl, b: &WaistControl, t: f32) -> WaistControl {
128 let t = t.clamp(0.0, 1.0);
129 WaistControl {
130 width: a.width + (b.width - a.width) * t,
131 depth: a.depth + (b.depth - a.depth) * t,
132 height_offset: a.height_offset + (b.height_offset - a.height_offset) * t,
133 }
134}
135
136#[allow(dead_code)]
138pub fn new_waist_control() -> WaistControl {
139 default_waist_control()
140}
141
142#[allow(dead_code)]
144pub fn set_waist_width(wc: &mut WaistControl, w: f32) {
145 wc.width = w;
146}
147
148#[allow(dead_code)]
150pub fn set_waist_depth(wc: &mut WaistControl, d: f32) {
151 wc.depth = d;
152}
153
154#[allow(dead_code)]
156pub fn waist_ratio_to_hip(wc: &WaistControl, hip_w: f32) -> f32 {
157 wc.width / hip_w.max(f32::EPSILON)
158}
159
160#[allow(dead_code)]
162pub fn waist_to_param(wc: &WaistControl, min_w: f32, max_w: f32) -> f32 {
163 let range = (max_w - min_w).max(f32::EPSILON);
164 ((wc.width - min_w) / range).clamp(0.0, 1.0)
165}
166
167#[allow(dead_code)]
169pub fn waist_from_param(param: f32, min_w: f32, max_w: f32) -> WaistControl {
170 WaistControl {
171 width: min_w + param.clamp(0.0, 1.0) * (max_w - min_w),
172 depth: 0.5,
173 height_offset: 0.0,
174 }
175}
176
177#[allow(dead_code)]
179pub fn waist_circumference_approx(wc: &WaistControl) -> f32 {
180 let a = wc.width;
181 let b = wc.depth;
182 let h = (a - b).powi(2) / ((a + b).powi(2) + f32::EPSILON);
183 PI * (a + b) * (1.0 + (3.0 * h) / (10.0 + (4.0 - 3.0 * h).sqrt()))
184}
185
186#[cfg(test)]
187mod tests {
188 use super::*;
189
190 #[test]
191 fn test_default_config() {
192 let cfg = default_waist_config();
193 assert!((cfg.min_width - 0.2).abs() < 1e-6);
194 assert_eq!(cfg.max_width, 1.0);
195 }
196
197 #[test]
198 fn test_new_state() {
199 let s = new_waist_state();
200 assert!((s.width - 0.5).abs() < 1e-6);
201 assert!((s.depth - 0.5).abs() < 1e-6);
202 }
203
204 #[test]
205 fn test_set_width_clamps() {
206 let cfg = default_waist_config();
207 let mut s = new_waist_state();
208 waist_set_width(&mut s, &cfg, 0.0);
209 assert!((s.width - cfg.min_width).abs() < 1e-6);
210 waist_set_width(&mut s, &cfg, 5.0);
211 assert_eq!(s.width, cfg.max_width);
212 }
213
214 #[test]
215 fn test_set_depth_clamps() {
216 let mut s = new_waist_state();
217 waist_set_depth(&mut s, 1.5);
218 assert_eq!(s.depth, 1.0);
219 waist_set_depth(&mut s, -0.5);
220 assert_eq!(s.depth, 0.0);
221 }
222
223 #[test]
224 fn test_circumference_positive() {
225 let s = new_waist_state();
226 let c = waist_compute_circumference(&s, 0.4);
227 assert!(c > 0.0);
228 }
229
230 #[test]
231 fn test_reset() {
232 let cfg = default_waist_config();
233 let mut s = new_waist_state();
234 waist_set_width(&mut s, &cfg, 0.9);
235 waist_reset(&mut s);
236 assert!((s.width - 0.5).abs() < 1e-6);
237 }
238
239 #[test]
240 fn test_to_weights_count() {
241 let s = new_waist_state();
242 assert_eq!(waist_to_weights(&s).len(), 3);
243 }
244
245 #[test]
246 fn test_to_json_has_keys() {
247 let s = new_waist_state();
248 let j = waist_to_json(&s);
249 assert!(j.contains("width"));
250 assert!(j.contains("height"));
251 }
252
253 #[test]
254 fn test_clamp_enforces_bounds() {
255 let cfg = default_waist_config();
256 let mut s = WaistState {
257 width: 0.0,
258 depth: 5.0,
259 height: -1.0,
260 };
261 waist_clamp(&mut s, &cfg);
262 assert!((s.width - cfg.min_width).abs() < 1e-6);
263 assert_eq!(s.depth, 1.0);
264 assert_eq!(s.height, 0.0);
265 }
266}