Skip to main content

oxihuman_morph/
bmi_body_shape_morph.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5pub struct BmiMorph {
6    pub bmi: f32,
7    pub underweight_blend: f32,
8    pub overweight_blend: f32,
9}
10
11pub fn new_bmi_morph(bmi: f32) -> BmiMorph {
12    let clamped = bmi.clamp(10.0, 60.0);
13    let underweight_blend = if clamped < 18.5 {
14        (18.5 - clamped) / 8.5
15    } else {
16        0.0
17    };
18    let overweight_blend = if clamped > 25.0 {
19        ((clamped - 25.0) / 35.0).min(1.0)
20    } else {
21        0.0
22    };
23    BmiMorph {
24        bmi: clamped,
25        underweight_blend,
26        overweight_blend,
27    }
28}
29
30pub fn bmi_category(bmi: f32) -> &'static str {
31    if bmi < 18.5 {
32        "underweight"
33    } else if bmi < 25.0 {
34        "normal"
35    } else if bmi < 30.0 {
36        "overweight"
37    } else {
38        "obese"
39    }
40}
41
42pub fn bmi_blend_weight(m: &BmiMorph) -> f32 {
43    (m.underweight_blend + m.overweight_blend).min(1.0)
44}
45
46pub fn bmi_is_healthy(m: &BmiMorph) -> bool {
47    m.bmi >= 18.5 && m.bmi <= 25.0
48}
49
50pub fn bmi_blend(a: &BmiMorph, b: &BmiMorph, t: f32) -> BmiMorph {
51    let t = t.clamp(0.0, 1.0);
52    let bmi = a.bmi + (b.bmi - a.bmi) * t;
53    new_bmi_morph(bmi)
54}
55
56#[cfg(test)]
57mod tests {
58    use super::*;
59
60    #[test]
61    fn test_new_bmi_morph_normal() {
62        /* BMI 22 is normal */
63        let m = new_bmi_morph(22.0);
64        assert!((m.bmi - 22.0).abs() < 1e-5);
65        assert!((m.underweight_blend).abs() < 1e-5);
66        assert!((m.overweight_blend).abs() < 1e-5);
67    }
68
69    #[test]
70    fn test_bmi_category() {
71        /* category checks */
72        assert_eq!(bmi_category(15.0), "underweight");
73        assert_eq!(bmi_category(22.0), "normal");
74        assert_eq!(bmi_category(27.0), "overweight");
75        assert_eq!(bmi_category(35.0), "obese");
76    }
77
78    #[test]
79    fn test_bmi_blend_weight_normal() {
80        /* normal bmi => zero blend */
81        let m = new_bmi_morph(22.0);
82        assert!((bmi_blend_weight(&m)).abs() < 1e-5);
83    }
84
85    #[test]
86    fn test_bmi_is_healthy() {
87        /* healthy range */
88        let m_healthy = new_bmi_morph(22.0);
89        let m_under = new_bmi_morph(16.0);
90        assert!(bmi_is_healthy(&m_healthy));
91        assert!(!bmi_is_healthy(&m_under));
92    }
93
94    #[test]
95    fn test_bmi_blend() {
96        /* blend between two morphs */
97        let a = new_bmi_morph(18.0);
98        let b = new_bmi_morph(30.0);
99        let c = bmi_blend(&a, &b, 0.5);
100        assert!((c.bmi - 24.0).abs() < 1e-4);
101    }
102
103    #[test]
104    fn test_bmi_category_obese() {
105        /* obese boundary */
106        assert_eq!(bmi_category(30.0), "obese");
107    }
108}