Skip to main content

oxihuman_morph/
body_weight_control.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3
4#![allow(dead_code)]
5
6//! Body weight morph control: adjusts overall body mass distribution.
7
8use std::f32::consts::PI;
9
10/// Configuration for body weight morphing.
11#[allow(dead_code)]
12#[derive(Debug, Clone)]
13pub struct BodyWeightConfig {
14    pub min_weight: f32,
15    pub max_weight: f32,
16    pub default_weight: f32,
17}
18
19/// Runtime state for body weight morph.
20#[allow(dead_code)]
21#[derive(Debug, Clone)]
22pub struct BodyWeightState {
23    pub overall: f32,
24    pub upper_body: f32,
25    pub lower_body: f32,
26    pub belly: f32,
27}
28
29#[allow(dead_code)]
30pub fn default_body_weight_config() -> BodyWeightConfig {
31    BodyWeightConfig {
32        min_weight: 0.0,
33        max_weight: 1.0,
34        default_weight: 0.5,
35    }
36}
37
38#[allow(dead_code)]
39pub fn new_body_weight_state() -> BodyWeightState {
40    BodyWeightState {
41        overall: 0.5,
42        upper_body: 0.5,
43        lower_body: 0.5,
44        belly: 0.3,
45    }
46}
47
48#[allow(dead_code)]
49pub fn bw_set_overall(state: &mut BodyWeightState, cfg: &BodyWeightConfig, v: f32) {
50    state.overall = v.clamp(cfg.min_weight, cfg.max_weight);
51}
52
53#[allow(dead_code)]
54pub fn bw_set_upper(state: &mut BodyWeightState, v: f32) {
55    state.upper_body = v.clamp(0.0, 1.0);
56}
57
58#[allow(dead_code)]
59pub fn bw_set_lower(state: &mut BodyWeightState, v: f32) {
60    state.lower_body = v.clamp(0.0, 1.0);
61}
62
63#[allow(dead_code)]
64pub fn bw_set_belly(state: &mut BodyWeightState, v: f32) {
65    state.belly = v.clamp(0.0, 1.0);
66}
67
68#[allow(dead_code)]
69pub fn bw_reset(state: &mut BodyWeightState) {
70    *state = new_body_weight_state();
71}
72
73#[allow(dead_code)]
74pub fn bw_to_weights(state: &BodyWeightState) -> Vec<(String, f32)> {
75    vec![
76        ("body_weight_overall".to_string(), state.overall),
77        ("body_weight_upper".to_string(), state.upper_body),
78        ("body_weight_lower".to_string(), state.lower_body),
79        ("body_weight_belly".to_string(), state.belly),
80    ]
81}
82
83#[allow(dead_code)]
84pub fn bw_to_json(state: &BodyWeightState) -> String {
85    format!(
86        r#"{{"overall":{:.4},"upper_body":{:.4},"lower_body":{:.4},"belly":{:.4}}}"#,
87        state.overall, state.upper_body, state.lower_body, state.belly
88    )
89}
90
91#[allow(dead_code)]
92pub fn bw_blend(a: &BodyWeightState, b: &BodyWeightState, t: f32) -> BodyWeightState {
93    let t = t.clamp(0.0, 1.0);
94    BodyWeightState {
95        overall: a.overall + (b.overall - a.overall) * t,
96        upper_body: a.upper_body + (b.upper_body - a.upper_body) * t,
97        lower_body: a.lower_body + (b.lower_body - a.lower_body) * t,
98        belly: a.belly + (b.belly - a.belly) * t,
99    }
100}
101
102/// Compute a sine-based distribution curve for weight placement.
103#[allow(dead_code)]
104pub fn bw_distribution_curve(state: &BodyWeightState, t: f32) -> f32 {
105    let t = t.clamp(0.0, 1.0);
106    state.overall * (PI * t).sin() * (state.upper_body + state.lower_body) * 0.5
107}
108
109#[cfg(test)]
110mod tests {
111    use super::*;
112
113    #[test]
114    fn test_default_config() {
115        let cfg = default_body_weight_config();
116        assert!((cfg.min_weight).abs() < 1e-6);
117        assert!((cfg.max_weight - 1.0).abs() < 1e-6);
118    }
119
120    #[test]
121    fn test_new_state() {
122        let s = new_body_weight_state();
123        assert!((s.overall - 0.5).abs() < 1e-6);
124        assert!((s.belly - 0.3).abs() < 1e-6);
125    }
126
127    #[test]
128    fn test_set_overall_clamps() {
129        let cfg = default_body_weight_config();
130        let mut s = new_body_weight_state();
131        bw_set_overall(&mut s, &cfg, 5.0);
132        assert!((s.overall - 1.0).abs() < 1e-6);
133        bw_set_overall(&mut s, &cfg, -1.0);
134        assert!(s.overall.abs() < 1e-6);
135    }
136
137    #[test]
138    fn test_set_upper() {
139        let mut s = new_body_weight_state();
140        bw_set_upper(&mut s, 0.8);
141        assert!((s.upper_body - 0.8).abs() < 1e-6);
142    }
143
144    #[test]
145    fn test_set_lower() {
146        let mut s = new_body_weight_state();
147        bw_set_lower(&mut s, 0.2);
148        assert!((s.lower_body - 0.2).abs() < 1e-6);
149    }
150
151    #[test]
152    fn test_set_belly() {
153        let mut s = new_body_weight_state();
154        bw_set_belly(&mut s, 0.9);
155        assert!((s.belly - 0.9).abs() < 1e-6);
156    }
157
158    #[test]
159    fn test_reset() {
160        let cfg = default_body_weight_config();
161        let mut s = new_body_weight_state();
162        bw_set_overall(&mut s, &cfg, 0.9);
163        bw_reset(&mut s);
164        assert!((s.overall - 0.5).abs() < 1e-6);
165    }
166
167    #[test]
168    fn test_to_weights_count() {
169        let s = new_body_weight_state();
170        assert_eq!(bw_to_weights(&s).len(), 4);
171    }
172
173    #[test]
174    fn test_blend_midpoint() {
175        let a = new_body_weight_state();
176        let mut b = new_body_weight_state();
177        b.overall = 1.0;
178        let mid = bw_blend(&a, &b, 0.5);
179        assert!((mid.overall - 0.75).abs() < 1e-6);
180    }
181
182    #[test]
183    fn test_distribution_curve() {
184        let s = new_body_weight_state();
185        let v = bw_distribution_curve(&s, 0.5);
186        assert!(v > 0.0);
187    }
188}