Skip to main content

oxihuman_morph/
hip_width_control.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3
4//! Hip width and shape morph control.
5
6#![allow(dead_code)]
7
8use std::f32::consts::PI;
9
10/// Hip width and shape parameters.
11#[allow(dead_code)]
12#[derive(Debug, Clone)]
13pub struct HipWidth {
14    pub width: f32,
15    pub depth: f32,
16    pub height: f32,
17}
18
19/// Returns a default `HipWidth`.
20#[allow(dead_code)]
21pub fn default_hip_width() -> HipWidth {
22    HipWidth {
23        width: 0.5,
24        depth: 0.5,
25        height: 0.5,
26    }
27}
28
29/// Applies hip width values to a weight slice.
30/// Indices: `[0]` = width, `[1]` = depth, `[2]` = height.
31#[allow(dead_code)]
32pub fn apply_hip_width(weights: &mut [f32], hw: &HipWidth) {
33    if !weights.is_empty() {
34        weights[0] = hw.width;
35    }
36    if weights.len() > 1 {
37        weights[1] = hw.depth;
38    }
39    if weights.len() > 2 {
40        weights[2] = hw.height;
41    }
42}
43
44/// Linearly blends two `HipWidth` values by parameter `t` in [0, 1].
45#[allow(dead_code)]
46pub fn hip_width_blend(a: &HipWidth, b: &HipWidth, t: f32) -> HipWidth {
47    let t = t.clamp(0.0, 1.0);
48    HipWidth {
49        width: a.width + (b.width - a.width) * t,
50        depth: a.depth + (b.depth - a.depth) * t,
51        height: a.height + (b.height - a.height) * t,
52    }
53}
54
55/// Computes the width-to-depth ratio of the hip.
56#[allow(dead_code)]
57pub fn hip_ratio(hw: &HipWidth) -> f32 {
58    if hw.depth.abs() < f32::EPSILON {
59        return 1.0;
60    }
61    hw.width / hw.depth
62}
63
64/// Approximate hip circumference using ellipse perimeter (Ramanujan approximation).
65#[allow(dead_code)]
66pub fn hip_circumference_approx(hw: &HipWidth, scale_m: f32) -> f32 {
67    let a = hw.width * scale_m;
68    let b = hw.depth * scale_m;
69    let h = (a - b).powi(2) / ((a + b).powi(2) + f32::EPSILON);
70    PI * (a + b) * (1.0 + (3.0 * h) / (10.0 + (4.0 - 3.0 * h).sqrt()))
71}
72
73#[cfg(test)]
74mod tests {
75    use super::*;
76
77    #[test]
78    fn test_default_hip_width() {
79        let hw = default_hip_width();
80        assert!((hw.width - 0.5).abs() < 1e-6);
81        assert!((hw.depth - 0.5).abs() < 1e-6);
82        assert!((hw.height - 0.5).abs() < 1e-6);
83    }
84
85    #[test]
86    fn test_apply_hip_width_full() {
87        let hw = HipWidth {
88            width: 0.6,
89            depth: 0.4,
90            height: 0.7,
91        };
92        let mut w = [0.0f32; 3];
93        apply_hip_width(&mut w, &hw);
94        assert!((w[0] - 0.6).abs() < 1e-6);
95        assert!((w[1] - 0.4).abs() < 1e-6);
96        assert!((w[2] - 0.7).abs() < 1e-6);
97    }
98
99    #[test]
100    fn test_apply_hip_width_short_slice() {
101        let hw = default_hip_width();
102        let mut w = [0.0f32; 1];
103        apply_hip_width(&mut w, &hw);
104        assert!((w[0] - 0.5).abs() < 1e-6);
105    }
106
107    #[test]
108    fn test_apply_hip_width_empty() {
109        let hw = default_hip_width();
110        let mut w: [f32; 0] = [];
111        apply_hip_width(&mut w, &hw); // must not panic
112    }
113
114    #[test]
115    fn test_blend_at_zero() {
116        let a = default_hip_width();
117        let b = HipWidth {
118            width: 1.0,
119            depth: 1.0,
120            height: 1.0,
121        };
122        let r = hip_width_blend(&a, &b, 0.0);
123        assert!((r.width - 0.5).abs() < 1e-6);
124    }
125
126    #[test]
127    fn test_blend_at_one() {
128        let a = default_hip_width();
129        let b = HipWidth {
130            width: 1.0,
131            depth: 1.0,
132            height: 1.0,
133        };
134        let r = hip_width_blend(&a, &b, 1.0);
135        assert!((r.width - 1.0).abs() < 1e-6);
136    }
137
138    #[test]
139    fn test_blend_clamps_t() {
140        let a = default_hip_width();
141        let b = HipWidth {
142            width: 1.0,
143            depth: 0.8,
144            height: 0.9,
145        };
146        let r = hip_width_blend(&a, &b, -1.0);
147        assert!((r.width - a.width).abs() < 1e-6);
148    }
149
150    #[test]
151    fn test_hip_ratio_equal_axes() {
152        let hw = HipWidth {
153            width: 0.5,
154            depth: 0.5,
155            height: 0.5,
156        };
157        assert!((hip_ratio(&hw) - 1.0).abs() < 1e-6);
158    }
159
160    #[test]
161    fn test_hip_ratio_wide() {
162        let hw = HipWidth {
163            width: 1.0,
164            depth: 0.5,
165            height: 0.5,
166        };
167        assert!((hip_ratio(&hw) - 2.0).abs() < 1e-6);
168    }
169
170    #[test]
171    fn test_hip_circumference_positive() {
172        let hw = default_hip_width();
173        let c = hip_circumference_approx(&hw, 0.5);
174        assert!(c > 0.0);
175    }
176}