Skip to main content

oxihuman_morph/
pear_shape_morph.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! Pear body shape morph — wider hips/thighs, narrower shoulders.
6
7/// Configuration for the pear shape morph.
8#[derive(Debug, Clone)]
9pub struct PearShapeConfig {
10    pub hip_width: f32,
11    pub thigh_fullness: f32,
12    pub shoulder_slim: f32,
13}
14
15impl Default for PearShapeConfig {
16    fn default() -> Self {
17        PearShapeConfig {
18            hip_width: 0.8,
19            thigh_fullness: 0.75,
20            shoulder_slim: 0.6,
21        }
22    }
23}
24
25/// State for the pear shape morph.
26#[derive(Debug, Clone)]
27pub struct PearShapeMorph {
28    pub intensity: f32,
29    pub config: PearShapeConfig,
30    pub enabled: bool,
31}
32
33/// Create a new pear shape morph.
34pub fn new_pear_shape_morph() -> PearShapeMorph {
35    PearShapeMorph {
36        intensity: 0.0,
37        config: PearShapeConfig::default(),
38        enabled: true,
39    }
40}
41
42/// Set intensity [0, 1].
43pub fn pear_set_intensity(m: &mut PearShapeMorph, v: f32) {
44    m.intensity = v.clamp(0.0, 1.0);
45}
46
47/// Hip width weight.
48pub fn pear_hip_width(m: &PearShapeMorph) -> f32 {
49    m.intensity * m.config.hip_width
50}
51
52/// Thigh fullness weight.
53pub fn pear_thigh_fullness(m: &PearShapeMorph) -> f32 {
54    m.intensity * m.config.thigh_fullness
55}
56
57/// Shoulder slimming weight.
58pub fn pear_shoulder_slim(m: &PearShapeMorph) -> f32 {
59    m.intensity * m.config.shoulder_slim
60}
61
62/// Hip-to-shoulder ratio estimate.
63pub fn pear_hip_shoulder_ratio(m: &PearShapeMorph) -> f32 {
64    1.0 + 0.35 * m.intensity
65}
66
67/// Serialise to JSON.
68pub fn pear_to_json(m: &PearShapeMorph) -> String {
69    format!(
70        r#"{{"intensity":{:.3},"hip_width":{:.3},"enabled":{}}}"#,
71        m.intensity,
72        pear_hip_width(m),
73        m.enabled
74    )
75}
76
77#[cfg(test)]
78mod tests {
79    use super::*;
80
81    #[test]
82    fn default_zero() {
83        let m = new_pear_shape_morph();
84        assert!((m.intensity - 0.0).abs() < 1e-6 /* zero */);
85    }
86
87    #[test]
88    fn clamp() {
89        let mut m = new_pear_shape_morph();
90        pear_set_intensity(&mut m, 2.0);
91        assert!((m.intensity - 1.0).abs() < 1e-6 /* clamped */);
92    }
93
94    #[test]
95    fn hip_width_at_max() {
96        let mut m = new_pear_shape_morph();
97        pear_set_intensity(&mut m, 1.0);
98        assert!((pear_hip_width(&m) - m.config.hip_width).abs() < 1e-6 /* correct */);
99    }
100
101    #[test]
102    fn thigh_fullness_zero_at_zero() {
103        let m = new_pear_shape_morph();
104        assert!((pear_thigh_fullness(&m) - 0.0).abs() < 1e-6 /* zero */);
105    }
106
107    #[test]
108    fn hip_shoulder_ratio_increases() {
109        let mut m = new_pear_shape_morph();
110        pear_set_intensity(&mut m, 0.0);
111        let r0 = pear_hip_shoulder_ratio(&m);
112        pear_set_intensity(&mut m, 1.0);
113        let r1 = pear_hip_shoulder_ratio(&m);
114        assert!(r1 > r0 /* wider hips relative to shoulders */);
115    }
116
117    #[test]
118    fn json_has_hip_width() {
119        let m = new_pear_shape_morph();
120        assert!(pear_to_json(&m).contains("hip_width") /* json has field */);
121    }
122
123    #[test]
124    fn enabled_default() {
125        let m = new_pear_shape_morph();
126        assert!(m.enabled /* enabled */);
127    }
128
129    #[test]
130    fn config_thigh_positive() {
131        let m = new_pear_shape_morph();
132        assert!(m.config.thigh_fullness > 0.0 /* valid */);
133    }
134}