Skip to main content

oxihuman_morph/
waist_control.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3
4//! Waist circumference/shape morph control.
5
6#![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/// Approximate circumference as an ellipse perimeter (Ramanujan approximation).
53#[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// ── New canonical structs/functions required by lib.rs re-export ──────────────
91
92/// Canonical waist control struct.
93#[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/// Returns a default `WaistControl`.
102#[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/// Applies waist control values to a weight slice.
112#[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/// Linearly blends two `WaistControl` values by `t` in [0, 1].
126#[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/// Create a default `WaistControl` (alias for `default_waist_control`).
137#[allow(dead_code)]
138pub fn new_waist_control() -> WaistControl {
139    default_waist_control()
140}
141
142/// Set the width field on a `WaistControl`.
143#[allow(dead_code)]
144pub fn set_waist_width(wc: &mut WaistControl, w: f32) {
145    wc.width = w;
146}
147
148/// Set the depth field on a `WaistControl`.
149#[allow(dead_code)]
150pub fn set_waist_depth(wc: &mut WaistControl, d: f32) {
151    wc.depth = d;
152}
153
154/// Return the waist-to-hip ratio given a hip width `hip_w`.
155#[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/// Convert waist width to a scalar parameter in [0, 1].
161#[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/// Reconstruct a `WaistControl` from a scalar parameter in [0, 1].
168#[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/// Approximate waist circumference using ellipse perimeter (Ramanujan approximation).
178#[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}