Skip to main content

oxihuman_morph/
limb_length_morph.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! Limb length asymmetry morph.
6
7/// Limb length asymmetry configuration.
8#[derive(Debug, Clone)]
9pub struct LimbLengthMorphConfig {
10    pub left_leg_scale: f32,
11    pub right_leg_scale: f32,
12    pub left_arm_scale: f32,
13    pub right_arm_scale: f32,
14}
15
16impl Default for LimbLengthMorphConfig {
17    fn default() -> Self {
18        Self {
19            left_leg_scale: 1.0,
20            right_leg_scale: 1.0,
21            left_arm_scale: 1.0,
22            right_arm_scale: 1.0,
23        }
24    }
25}
26
27/// Limb length morph state.
28#[derive(Debug, Clone)]
29pub struct LimbLengthMorph {
30    pub config: LimbLengthMorphConfig,
31    pub intensity: f32,
32    pub enabled: bool,
33}
34
35impl LimbLengthMorph {
36    pub fn new() -> Self {
37        Self {
38            config: LimbLengthMorphConfig::default(),
39            intensity: 1.0,
40            enabled: true,
41        }
42    }
43}
44
45impl Default for LimbLengthMorph {
46    fn default() -> Self {
47        Self::new()
48    }
49}
50
51/// Create a new LimbLengthMorph.
52pub fn new_limb_length_morph() -> LimbLengthMorph {
53    LimbLengthMorph::new()
54}
55
56/// Set left leg length scale (0.5–1.5).
57pub fn limb_set_left_leg(morph: &mut LimbLengthMorph, scale: f32) {
58    morph.config.left_leg_scale = scale.clamp(0.5, 1.5);
59}
60
61/// Set right leg length scale.
62pub fn limb_set_right_leg(morph: &mut LimbLengthMorph, scale: f32) {
63    morph.config.right_leg_scale = scale.clamp(0.5, 1.5);
64}
65
66/// Set left arm length scale.
67pub fn limb_set_left_arm(morph: &mut LimbLengthMorph, scale: f32) {
68    morph.config.left_arm_scale = scale.clamp(0.5, 1.5);
69}
70
71/// Set right arm length scale.
72pub fn limb_set_right_arm(morph: &mut LimbLengthMorph, scale: f32) {
73    morph.config.right_arm_scale = scale.clamp(0.5, 1.5);
74}
75
76/// Compute leg length discrepancy (absolute difference).
77pub fn limb_leg_discrepancy(morph: &LimbLengthMorph) -> f32 {
78    (morph.config.left_leg_scale - morph.config.right_leg_scale).abs()
79}
80
81/// Serialize to JSON.
82pub fn limb_length_to_json(morph: &LimbLengthMorph) -> String {
83    format!(
84        r#"{{"intensity":{},"left_leg":{},"right_leg":{},"left_arm":{},"right_arm":{}}}"#,
85        morph.intensity,
86        morph.config.left_leg_scale,
87        morph.config.right_leg_scale,
88        morph.config.left_arm_scale,
89        morph.config.right_arm_scale,
90    )
91}
92
93#[cfg(test)]
94mod tests {
95    use super::*;
96
97    #[test]
98    fn test_new() {
99        let m = new_limb_length_morph();
100        assert!((m.config.left_leg_scale - 1.0).abs() < 1e-6 /* default 1 */);
101    }
102
103    #[test]
104    fn test_left_leg_clamp_high() {
105        let mut m = new_limb_length_morph();
106        limb_set_left_leg(&mut m, 3.0);
107        assert!((m.config.left_leg_scale - 1.5).abs() < 1e-6 /* clamped */);
108    }
109
110    #[test]
111    fn test_left_leg_clamp_low() {
112        let mut m = new_limb_length_morph();
113        limb_set_left_leg(&mut m, 0.1);
114        assert!((m.config.left_leg_scale - 0.5).abs() < 1e-6 /* clamped */);
115    }
116
117    #[test]
118    fn test_right_leg() {
119        let mut m = new_limb_length_morph();
120        limb_set_right_leg(&mut m, 0.9);
121        assert!((m.config.right_leg_scale - 0.9).abs() < 1e-6 /* stored */);
122    }
123
124    #[test]
125    fn test_left_arm() {
126        let mut m = new_limb_length_morph();
127        limb_set_left_arm(&mut m, 1.1);
128        assert!((m.config.left_arm_scale - 1.1).abs() < 1e-6 /* stored */);
129    }
130
131    #[test]
132    fn test_discrepancy_zero() {
133        let m = new_limb_length_morph();
134        assert!((limb_leg_discrepancy(&m) - 0.0).abs() < 1e-6 /* symmetric */);
135    }
136
137    #[test]
138    fn test_discrepancy_nonzero() {
139        let mut m = new_limb_length_morph();
140        limb_set_left_leg(&mut m, 1.1);
141        limb_set_right_leg(&mut m, 0.9);
142        assert!((limb_leg_discrepancy(&m) - 0.2).abs() < 1e-4 /* 0.2 diff */);
143    }
144
145    #[test]
146    fn test_json_key() {
147        let m = new_limb_length_morph();
148        let j = limb_length_to_json(&m);
149        assert!(j.contains("left_leg") /* key */);
150    }
151
152    #[test]
153    fn test_default() {
154        let m = LimbLengthMorph::default();
155        assert!(m.enabled /* enabled */);
156    }
157}