Skip to main content

oxihuman_morph/
infant_morph.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! Infant/baby body proportion morph.
6
7/// Configuration for the infant morph.
8#[derive(Debug, Clone)]
9pub struct InfantMorphConfig {
10    /// Head-to-body ratio multiplier (infants ~1/4 body height).
11    pub head_ratio: f32,
12    /// Limb shortening factor relative to adult.
13    pub limb_scale: f32,
14    /// Belly roundness.
15    pub belly_roundness: f32,
16}
17
18impl Default for InfantMorphConfig {
19    fn default() -> Self {
20        InfantMorphConfig {
21            head_ratio: 1.0,
22            limb_scale: 0.45,
23            belly_roundness: 0.8,
24        }
25    }
26}
27
28/// State for the infant body morph.
29#[derive(Debug, Clone)]
30pub struct InfantMorph {
31    /// Age in months (0–24).
32    pub age_months: f32,
33    pub config: InfantMorphConfig,
34    pub enabled: bool,
35}
36
37/// Create a new infant morph at birth (0 months).
38pub fn new_infant_morph() -> InfantMorph {
39    InfantMorph {
40        age_months: 0.0,
41        config: InfantMorphConfig::default(),
42        enabled: true,
43    }
44}
45
46/// Set age in months (clamped to 0–24).
47pub fn im_set_age(m: &mut InfantMorph, months: f32) {
48    m.age_months = months.clamp(0.0, 24.0);
49}
50
51/// Normalised morphing weight (0 = newborn, 1 = 24 months).
52pub fn im_weight(m: &InfantMorph) -> f32 {
53    m.age_months / 24.0
54}
55
56/// Head scale factor (infants have proportionally larger heads).
57pub fn im_head_scale(m: &InfantMorph) -> f32 {
58    let t = im_weight(m);
59    m.config.head_ratio * (1.0 - 0.3 * t)
60}
61
62/// Limb length scale relative to adult norm.
63pub fn im_limb_scale(m: &InfantMorph) -> f32 {
64    let t = im_weight(m);
65    m.config.limb_scale + 0.1 * t
66}
67
68/// Serialise to JSON string.
69pub fn im_to_json(m: &InfantMorph) -> String {
70    format!(
71        r#"{{"age_months":{:.1},"enabled":{},"head_scale":{:.3}}}"#,
72        m.age_months,
73        m.enabled,
74        im_head_scale(m)
75    )
76}
77
78#[cfg(test)]
79mod tests {
80    use super::*;
81
82    #[test]
83    fn default_is_newborn() {
84        let m = new_infant_morph();
85        assert!((m.age_months - 0.0).abs() < 1e-6 /* newborn age */);
86    }
87
88    #[test]
89    fn set_age_clamps_upper() {
90        let mut m = new_infant_morph();
91        im_set_age(&mut m, 100.0);
92        assert!((m.age_months - 24.0).abs() < 1e-6 /* clamped */);
93    }
94
95    #[test]
96    fn weight_at_24_months_is_one() {
97        let mut m = new_infant_morph();
98        im_set_age(&mut m, 24.0);
99        assert!((im_weight(&m) - 1.0).abs() < 1e-6 /* full weight */);
100    }
101
102    #[test]
103    fn head_scale_decreases_with_age() {
104        let mut m = new_infant_morph();
105        im_set_age(&mut m, 0.0);
106        let h0 = im_head_scale(&m);
107        im_set_age(&mut m, 24.0);
108        let h24 = im_head_scale(&m);
109        assert!(h0 > h24 /* head relatively larger at birth */);
110    }
111
112    #[test]
113    fn limb_scale_increases_with_age() {
114        let mut m = new_infant_morph();
115        im_set_age(&mut m, 0.0);
116        let l0 = im_limb_scale(&m);
117        im_set_age(&mut m, 24.0);
118        let l24 = im_limb_scale(&m);
119        assert!(l24 > l0 /* limbs grow with age */);
120    }
121
122    #[test]
123    fn to_json_has_age() {
124        let mut m = new_infant_morph();
125        im_set_age(&mut m, 6.0);
126        assert!(im_to_json(&m).contains("6.0") /* json has age */);
127    }
128
129    #[test]
130    fn enabled_toggle() {
131        let mut m = new_infant_morph();
132        m.enabled = false;
133        assert!(!m.enabled /* disabled */);
134    }
135
136    #[test]
137    fn belly_roundness_in_config() {
138        let m = new_infant_morph();
139        assert!(m.config.belly_roundness > 0.0 /* non-zero belly roundness */);
140    }
141}