Skip to main content

oxihuman_morph/
apple_shape_morph.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! Apple body shape morph — weight centred around mid-section/abdomen.
6
7/// Configuration for the apple shape morph.
8#[derive(Debug, Clone)]
9pub struct AppleShapeConfig {
10    pub abdomen_fullness: f32,
11    pub chest_roundness: f32,
12    pub limb_slenderness: f32,
13}
14
15impl Default for AppleShapeConfig {
16    fn default() -> Self {
17        AppleShapeConfig {
18            abdomen_fullness: 0.85,
19            chest_roundness: 0.6,
20            limb_slenderness: 0.7,
21        }
22    }
23}
24
25/// State for the apple shape morph.
26#[derive(Debug, Clone)]
27pub struct AppleShapeMorph {
28    pub intensity: f32,
29    pub config: AppleShapeConfig,
30    pub enabled: bool,
31}
32
33/// Create a new apple shape morph.
34pub fn new_apple_shape_morph() -> AppleShapeMorph {
35    AppleShapeMorph {
36        intensity: 0.0,
37        config: AppleShapeConfig::default(),
38        enabled: true,
39    }
40}
41
42/// Set intensity [0, 1].
43pub fn apple_set_intensity(m: &mut AppleShapeMorph, v: f32) {
44    m.intensity = v.clamp(0.0, 1.0);
45}
46
47/// Abdomen fullness weight.
48pub fn apple_abdomen(m: &AppleShapeMorph) -> f32 {
49    m.intensity * m.config.abdomen_fullness
50}
51
52/// Chest roundness weight.
53pub fn apple_chest(m: &AppleShapeMorph) -> f32 {
54    m.intensity * m.config.chest_roundness
55}
56
57/// Limb slenderness weight (apple shapes tend to have slimmer limbs).
58pub fn apple_limb_slim(m: &AppleShapeMorph) -> f32 {
59    m.intensity * m.config.limb_slenderness
60}
61
62/// Waist circumference estimate (relative to neutral).
63pub fn apple_waist_scale(m: &AppleShapeMorph) -> f32 {
64    1.0 + 0.3 * apple_abdomen(m)
65}
66
67/// Serialise to JSON.
68pub fn apple_to_json(m: &AppleShapeMorph) -> String {
69    format!(
70        r#"{{"intensity":{:.3},"abdomen":{:.3},"enabled":{}}}"#,
71        m.intensity,
72        apple_abdomen(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_apple_shape_morph();
84        assert!((m.intensity - 0.0).abs() < 1e-6 /* zero */);
85    }
86
87    #[test]
88    fn clamp() {
89        let mut m = new_apple_shape_morph();
90        apple_set_intensity(&mut m, -5.0);
91        assert!((m.intensity - 0.0).abs() < 1e-6 /* clamped to 0 */);
92    }
93
94    #[test]
95    fn abdomen_at_max() {
96        let mut m = new_apple_shape_morph();
97        apple_set_intensity(&mut m, 1.0);
98        assert!((apple_abdomen(&m) - m.config.abdomen_fullness).abs() < 1e-6 /* correct */);
99    }
100
101    #[test]
102    fn chest_zero_at_zero() {
103        let m = new_apple_shape_morph();
104        assert!((apple_chest(&m) - 0.0).abs() < 1e-6 /* zero */);
105    }
106
107    #[test]
108    fn waist_scale_increases() {
109        let mut m = new_apple_shape_morph();
110        apple_set_intensity(&mut m, 0.0);
111        let w0 = apple_waist_scale(&m);
112        apple_set_intensity(&mut m, 1.0);
113        let w1 = apple_waist_scale(&m);
114        assert!(w1 > w0 /* larger waist with intensity */);
115    }
116
117    #[test]
118    fn limb_slim_positive_at_half() {
119        let mut m = new_apple_shape_morph();
120        apple_set_intensity(&mut m, 0.5);
121        assert!(apple_limb_slim(&m) > 0.0 /* positive slim */);
122    }
123
124    #[test]
125    fn json_has_abdomen() {
126        let m = new_apple_shape_morph();
127        assert!(apple_to_json(&m).contains("abdomen") /* json has field */);
128    }
129
130    #[test]
131    fn enabled_default() {
132        let m = new_apple_shape_morph();
133        assert!(m.enabled /* enabled */);
134    }
135}