Skip to main content

oxihuman_morph/
pregnancy_morph.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! Pregnancy body shape progression morph.
6
7/// Trimester stage of pregnancy.
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum Trimester {
10    First,
11    Second,
12    Third,
13    PostPartum,
14}
15
16/// Configuration for the pregnancy morph.
17#[derive(Debug, Clone)]
18pub struct PregnancyMorphConfig {
19    pub max_belly_scale: f32,
20    pub breast_scale_factor: f32,
21    pub hip_expansion: f32,
22}
23
24impl Default for PregnancyMorphConfig {
25    fn default() -> Self {
26        PregnancyMorphConfig {
27            max_belly_scale: 1.0,
28            breast_scale_factor: 0.4,
29            hip_expansion: 0.15,
30        }
31    }
32}
33
34/// State for the pregnancy body morph.
35#[derive(Debug, Clone)]
36pub struct PregnancyMorph {
37    pub weeks: f32,
38    pub config: PregnancyMorphConfig,
39    pub enabled: bool,
40}
41
42/// Create a new pregnancy morph at zero weeks.
43pub fn new_pregnancy_morph() -> PregnancyMorph {
44    PregnancyMorph {
45        weeks: 0.0,
46        config: PregnancyMorphConfig::default(),
47        enabled: true,
48    }
49}
50
51/// Set gestational age in weeks (0–42).
52pub fn pm_set_weeks(m: &mut PregnancyMorph, weeks: f32) {
53    m.weeks = weeks.clamp(0.0, 42.0);
54}
55
56/// Return the current trimester based on weeks.
57pub fn pm_trimester(m: &PregnancyMorph) -> Trimester {
58    if m.weeks < 13.0 {
59        Trimester::First
60    } else if m.weeks < 27.0 {
61        Trimester::Second
62    } else if m.weeks <= 42.0 {
63        Trimester::Third
64    } else {
65        Trimester::PostPartum
66    }
67}
68
69/// Normalised belly weight in [0, 1].
70pub fn pm_belly_weight(m: &PregnancyMorph) -> f32 {
71    let t = (m.weeks / 40.0).clamp(0.0, 1.0);
72    t * t * m.config.max_belly_scale
73}
74
75/// Breast volume scale (additive delta on top of base).
76pub fn pm_breast_delta(m: &PregnancyMorph) -> f32 {
77    let t = (m.weeks / 40.0).clamp(0.0, 1.0);
78    t * m.config.breast_scale_factor
79}
80
81/// Serialise state to a simple JSON string.
82pub fn pm_to_json(m: &PregnancyMorph) -> String {
83    format!(
84        r#"{{"weeks":{:.1},"enabled":{},"belly_weight":{:.3}}}"#,
85        m.weeks,
86        m.enabled,
87        pm_belly_weight(m)
88    )
89}
90
91#[cfg(test)]
92mod tests {
93    use super::*;
94
95    #[test]
96    fn default_is_zero_weeks() {
97        let m = new_pregnancy_morph();
98        assert!((m.weeks - 0.0).abs() < 1e-6 /* zero weeks */);
99    }
100
101    #[test]
102    fn set_weeks_clamps() {
103        let mut m = new_pregnancy_morph();
104        pm_set_weeks(&mut m, 999.0);
105        assert!((m.weeks - 42.0).abs() < 1e-6 /* clamped to max */);
106        pm_set_weeks(&mut m, -5.0);
107        assert!((m.weeks - 0.0).abs() < 1e-6 /* clamped to min */);
108    }
109
110    #[test]
111    fn trimester_first() {
112        let mut m = new_pregnancy_morph();
113        pm_set_weeks(&mut m, 8.0);
114        assert_eq!(
115            pm_trimester(&m),
116            Trimester::First /* 8 weeks = first */
117        );
118    }
119
120    #[test]
121    fn trimester_second() {
122        let mut m = new_pregnancy_morph();
123        pm_set_weeks(&mut m, 20.0);
124        assert_eq!(
125            pm_trimester(&m),
126            Trimester::Second /* 20 weeks = second */
127        );
128    }
129
130    #[test]
131    fn trimester_third() {
132        let mut m = new_pregnancy_morph();
133        pm_set_weeks(&mut m, 35.0);
134        assert_eq!(
135            pm_trimester(&m),
136            Trimester::Third /* 35 weeks = third */
137        );
138    }
139
140    #[test]
141    fn belly_weight_increases_with_weeks() {
142        let mut m = new_pregnancy_morph();
143        pm_set_weeks(&mut m, 10.0);
144        let w10 = pm_belly_weight(&m);
145        pm_set_weeks(&mut m, 30.0);
146        let w30 = pm_belly_weight(&m);
147        assert!(w30 > w10 /* belly grows over time */);
148    }
149
150    #[test]
151    fn breast_delta_increases_with_weeks() {
152        let mut m = new_pregnancy_morph();
153        pm_set_weeks(&mut m, 5.0);
154        let d5 = pm_breast_delta(&m);
155        pm_set_weeks(&mut m, 38.0);
156        let d38 = pm_breast_delta(&m);
157        assert!(d38 > d5 /* breast grows over time */);
158    }
159
160    #[test]
161    fn to_json_contains_weeks() {
162        let mut m = new_pregnancy_morph();
163        pm_set_weeks(&mut m, 20.0);
164        let j = pm_to_json(&m);
165        assert!(j.contains("20.0") /* JSON includes weeks */);
166    }
167
168    #[test]
169    fn enabled_flag() {
170        let mut m = new_pregnancy_morph();
171        m.enabled = false;
172        assert!(!m.enabled /* disabled morph */);
173    }
174}