Skip to main content

oxihuman_morph/
rectangle_body_morph.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! Rectangle/straight body figure morph — minimal waist definition.
6
7/// Configuration for the rectangle body morph.
8#[derive(Debug, Clone)]
9pub struct RectangleBodyConfig {
10    pub waist_fullness: f32,
11    pub shoulder_hip_balance: f32,
12}
13
14impl Default for RectangleBodyConfig {
15    fn default() -> Self {
16        RectangleBodyConfig {
17            waist_fullness: 0.7,
18            shoulder_hip_balance: 0.95,
19        }
20    }
21}
22
23/// State for the rectangle body morph.
24#[derive(Debug, Clone)]
25pub struct RectangleBodyMorph {
26    pub intensity: f32,
27    pub config: RectangleBodyConfig,
28    pub enabled: bool,
29}
30
31/// Create a new rectangle body morph.
32pub fn new_rectangle_body_morph() -> RectangleBodyMorph {
33    RectangleBodyMorph {
34        intensity: 0.0,
35        config: RectangleBodyConfig::default(),
36        enabled: true,
37    }
38}
39
40/// Set intensity [0, 1].
41pub fn rect_set_intensity(m: &mut RectangleBodyMorph, v: f32) {
42    m.intensity = v.clamp(0.0, 1.0);
43}
44
45/// Waist fullness (reduces waist cinch).
46pub fn rect_waist_fullness(m: &RectangleBodyMorph) -> f32 {
47    m.intensity * m.config.waist_fullness
48}
49
50/// Shoulder-to-hip balance — closer to 1 = more rectangular.
51pub fn rect_shoulder_hip_balance(m: &RectangleBodyMorph) -> f32 {
52    let base = 0.8_f32;
53    base + 0.15 * m.intensity * m.config.shoulder_hip_balance
54}
55
56/// Overall straightness score [0, 1].
57pub fn rect_straightness(m: &RectangleBodyMorph) -> f32 {
58    m.intensity
59}
60
61/// Serialise to JSON.
62pub fn rect_to_json(m: &RectangleBodyMorph) -> String {
63    format!(
64        r#"{{"intensity":{:.3},"waist_fullness":{:.3},"enabled":{}}}"#,
65        m.intensity,
66        rect_waist_fullness(m),
67        m.enabled
68    )
69}
70
71#[cfg(test)]
72mod tests {
73    use super::*;
74
75    #[test]
76    fn default_zero() {
77        let m = new_rectangle_body_morph();
78        assert!((m.intensity - 0.0).abs() < 1e-6 /* zero */);
79    }
80
81    #[test]
82    fn clamp() {
83        let mut m = new_rectangle_body_morph();
84        rect_set_intensity(&mut m, 3.0);
85        assert!((m.intensity - 1.0).abs() < 1e-6 /* clamped */);
86    }
87
88    #[test]
89    fn waist_fullness_zero_at_zero() {
90        let m = new_rectangle_body_morph();
91        assert!((rect_waist_fullness(&m) - 0.0).abs() < 1e-6 /* zero */);
92    }
93
94    #[test]
95    fn balance_increases() {
96        let mut m = new_rectangle_body_morph();
97        rect_set_intensity(&mut m, 0.0);
98        let b0 = rect_shoulder_hip_balance(&m);
99        rect_set_intensity(&mut m, 1.0);
100        let b1 = rect_shoulder_hip_balance(&m);
101        assert!(b1 > b0 /* more balance at higher intensity */);
102    }
103
104    #[test]
105    fn straightness_equals_intensity() {
106        let mut m = new_rectangle_body_morph();
107        rect_set_intensity(&mut m, 0.7);
108        assert!((rect_straightness(&m) - 0.7).abs() < 1e-6 /* correct */);
109    }
110
111    #[test]
112    fn json_has_waist() {
113        let m = new_rectangle_body_morph();
114        assert!(rect_to_json(&m).contains("waist") /* json has field */);
115    }
116
117    #[test]
118    fn enabled_default() {
119        let m = new_rectangle_body_morph();
120        assert!(m.enabled /* enabled */);
121    }
122
123    #[test]
124    fn config_shoulder_hip_balance_positive() {
125        let m = new_rectangle_body_morph();
126        assert!(m.config.shoulder_hip_balance > 0.0 /* valid */);
127    }
128}