oxihuman_morph/
body_taper_control.rs1#![allow(dead_code)]
5
6use std::f32::consts::PI;
9
10#[allow(dead_code)]
12#[derive(Debug, Clone)]
13pub struct BodyTaperConfig {
14 pub min_taper: f32,
15 pub max_taper: f32,
16}
17
18#[allow(dead_code)]
20#[derive(Debug, Clone)]
21pub struct BodyTaperState {
22 pub shoulder_width: f32,
23 pub waist_width: f32,
24 pub hip_width: f32,
25}
26
27#[allow(dead_code)]
28pub fn default_body_taper_config() -> BodyTaperConfig {
29 BodyTaperConfig {
30 min_taper: 0.0,
31 max_taper: 1.0,
32 }
33}
34
35#[allow(dead_code)]
36pub fn new_body_taper_state() -> BodyTaperState {
37 BodyTaperState {
38 shoulder_width: 0.6,
39 waist_width: 0.4,
40 hip_width: 0.5,
41 }
42}
43
44#[allow(dead_code)]
45pub fn bt_set_shoulder(state: &mut BodyTaperState, cfg: &BodyTaperConfig, v: f32) {
46 state.shoulder_width = v.clamp(cfg.min_taper, cfg.max_taper);
47}
48
49#[allow(dead_code)]
50pub fn bt_set_waist(state: &mut BodyTaperState, cfg: &BodyTaperConfig, v: f32) {
51 state.waist_width = v.clamp(cfg.min_taper, cfg.max_taper);
52}
53
54#[allow(dead_code)]
55pub fn bt_set_hip(state: &mut BodyTaperState, cfg: &BodyTaperConfig, v: f32) {
56 state.hip_width = v.clamp(cfg.min_taper, cfg.max_taper);
57}
58
59#[allow(dead_code)]
60pub fn bt_reset(state: &mut BodyTaperState) {
61 *state = new_body_taper_state();
62}
63
64#[allow(dead_code)]
65pub fn bt_taper_ratio(state: &BodyTaperState) -> f32 {
66 if state.shoulder_width.abs() < 1e-9 {
67 return 0.0;
68 }
69 state.waist_width / state.shoulder_width
70}
71
72#[allow(dead_code)]
73pub fn bt_to_weights(state: &BodyTaperState) -> Vec<(String, f32)> {
74 vec![
75 ("body_shoulder_width".to_string(), state.shoulder_width),
76 ("body_waist_width".to_string(), state.waist_width),
77 ("body_hip_width".to_string(), state.hip_width),
78 ]
79}
80
81#[allow(dead_code)]
82pub fn bt_to_json(state: &BodyTaperState) -> String {
83 format!(
84 r#"{{"shoulder_width":{:.4},"waist_width":{:.4},"hip_width":{:.4}}}"#,
85 state.shoulder_width, state.waist_width, state.hip_width
86 )
87}
88
89#[allow(dead_code)]
90pub fn bt_blend(a: &BodyTaperState, b: &BodyTaperState, t: f32) -> BodyTaperState {
91 let t = t.clamp(0.0, 1.0);
92 BodyTaperState {
93 shoulder_width: a.shoulder_width + (b.shoulder_width - a.shoulder_width) * t,
94 waist_width: a.waist_width + (b.waist_width - a.waist_width) * t,
95 hip_width: a.hip_width + (b.hip_width - a.hip_width) * t,
96 }
97}
98
99#[allow(dead_code)]
101pub fn bt_silhouette_area(state: &BodyTaperState, height: f32) -> f32 {
102 let _ = PI; 0.5 * (state.shoulder_width + state.hip_width) * height
104}
105
106#[cfg(test)]
107mod tests {
108 use super::*;
109
110 #[test]
111 fn test_default_config() {
112 let cfg = default_body_taper_config();
113 assert!(cfg.min_taper.abs() < 1e-6);
114 assert!((cfg.max_taper - 1.0).abs() < 1e-6);
115 }
116
117 #[test]
118 fn test_new_state() {
119 let s = new_body_taper_state();
120 assert!((s.shoulder_width - 0.6).abs() < 1e-6);
121 assert!((s.waist_width - 0.4).abs() < 1e-6);
122 }
123
124 #[test]
125 fn test_set_shoulder_clamps() {
126 let cfg = default_body_taper_config();
127 let mut s = new_body_taper_state();
128 bt_set_shoulder(&mut s, &cfg, 5.0);
129 assert!((s.shoulder_width - 1.0).abs() < 1e-6);
130 }
131
132 #[test]
133 fn test_set_waist() {
134 let cfg = default_body_taper_config();
135 let mut s = new_body_taper_state();
136 bt_set_waist(&mut s, &cfg, 0.3);
137 assert!((s.waist_width - 0.3).abs() < 1e-6);
138 }
139
140 #[test]
141 fn test_set_hip() {
142 let cfg = default_body_taper_config();
143 let mut s = new_body_taper_state();
144 bt_set_hip(&mut s, &cfg, 0.7);
145 assert!((s.hip_width - 0.7).abs() < 1e-6);
146 }
147
148 #[test]
149 fn test_reset() {
150 let cfg = default_body_taper_config();
151 let mut s = new_body_taper_state();
152 bt_set_shoulder(&mut s, &cfg, 0.9);
153 bt_reset(&mut s);
154 assert!((s.shoulder_width - 0.6).abs() < 1e-6);
155 }
156
157 #[test]
158 fn test_taper_ratio() {
159 let s = new_body_taper_state();
160 let r = bt_taper_ratio(&s);
161 assert!((r - 0.4 / 0.6).abs() < 1e-4);
162 }
163
164 #[test]
165 fn test_to_weights() {
166 let s = new_body_taper_state();
167 assert_eq!(bt_to_weights(&s).len(), 3);
168 }
169
170 #[test]
171 fn test_blend() {
172 let a = new_body_taper_state();
173 let mut b = new_body_taper_state();
174 b.shoulder_width = 1.0;
175 let mid = bt_blend(&a, &b, 0.5);
176 assert!((mid.shoulder_width - 0.8).abs() < 1e-6);
177 }
178
179 #[test]
180 fn test_silhouette_area() {
181 let s = new_body_taper_state();
182 let area = bt_silhouette_area(&s, 1.0);
183 assert!(area > 0.0);
184 }
185}