Skip to main content

oxihuman_morph/
gynoid_proportion.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! Gynoid body proportion morph — weight centred lower body/hips.
6
7/// Configuration for the gynoid proportion morph.
8#[derive(Debug, Clone)]
9pub struct GynoidProportionConfig {
10    pub hip_fullness: f32,
11    pub thigh_girth: f32,
12    pub upper_body_slim: f32,
13}
14
15impl Default for GynoidProportionConfig {
16    fn default() -> Self {
17        GynoidProportionConfig {
18            hip_fullness: 0.8,
19            thigh_girth: 0.7,
20            upper_body_slim: 0.5,
21        }
22    }
23}
24
25/// State for the gynoid proportion morph.
26#[derive(Debug, Clone)]
27pub struct GynoidProportion {
28    pub intensity: f32,
29    pub config: GynoidProportionConfig,
30    pub enabled: bool,
31}
32
33/// Create a new gynoid proportion morph.
34pub fn new_gynoid_proportion() -> GynoidProportion {
35    GynoidProportion {
36        intensity: 0.0,
37        config: GynoidProportionConfig::default(),
38        enabled: true,
39    }
40}
41
42/// Set intensity [0, 1].
43pub fn gyn_set_intensity(m: &mut GynoidProportion, v: f32) {
44    m.intensity = v.clamp(0.0, 1.0);
45}
46
47/// Hip fullness weight.
48pub fn gyn_hip_fullness(m: &GynoidProportion) -> f32 {
49    m.intensity * m.config.hip_fullness
50}
51
52/// Thigh girth weight.
53pub fn gyn_thigh_girth(m: &GynoidProportion) -> f32 {
54    m.intensity * m.config.thigh_girth
55}
56
57/// Upper body slimming weight.
58pub fn gyn_upper_slim(m: &GynoidProportion) -> f32 {
59    m.intensity * m.config.upper_body_slim
60}
61
62/// Serialise to JSON.
63pub fn gyn_to_json(m: &GynoidProportion) -> String {
64    format!(
65        r#"{{"intensity":{:.3},"hip_fullness":{:.3},"enabled":{}}}"#,
66        m.intensity,
67        gyn_hip_fullness(m),
68        m.enabled
69    )
70}
71
72#[cfg(test)]
73mod tests {
74    use super::*;
75
76    #[test]
77    fn default_zero() {
78        let m = new_gynoid_proportion();
79        assert!((m.intensity - 0.0).abs() < 1e-6 /* zero */);
80    }
81
82    #[test]
83    fn clamp_intensity() {
84        let mut m = new_gynoid_proportion();
85        gyn_set_intensity(&mut m, 2.0);
86        assert!((m.intensity - 1.0).abs() < 1e-6 /* clamped */);
87    }
88
89    #[test]
90    fn hip_fullness_at_max() {
91        let mut m = new_gynoid_proportion();
92        gyn_set_intensity(&mut m, 1.0);
93        assert!((gyn_hip_fullness(&m) - m.config.hip_fullness).abs() < 1e-6 /* correct */);
94    }
95
96    #[test]
97    fn thigh_girth_zero_at_zero() {
98        let m = new_gynoid_proportion();
99        assert!((gyn_thigh_girth(&m) - 0.0).abs() < 1e-6 /* zero */);
100    }
101
102    #[test]
103    fn upper_slim_increases() {
104        let mut m = new_gynoid_proportion();
105        gyn_set_intensity(&mut m, 0.5);
106        let s5 = gyn_upper_slim(&m);
107        gyn_set_intensity(&mut m, 1.0);
108        let s10 = gyn_upper_slim(&m);
109        assert!(s10 > s5 /* more slim at higher intensity */);
110    }
111
112    #[test]
113    fn json_has_intensity() {
114        let mut m = new_gynoid_proportion();
115        gyn_set_intensity(&mut m, 0.3);
116        assert!(gyn_to_json(&m).contains("0.300") /* intensity in json */);
117    }
118
119    #[test]
120    fn enabled_default() {
121        let m = new_gynoid_proportion();
122        assert!(m.enabled /* enabled */);
123    }
124
125    #[test]
126    fn thigh_girth_config_positive() {
127        let m = new_gynoid_proportion();
128        assert!(m.config.thigh_girth > 0.0 /* valid */);
129    }
130}