Skip to main content

oxihuman_morph/
muscle_tone_morph.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! Muscle tone/definition morph stub.
6
7/// Muscle group identifier.
8#[derive(Debug, Clone, Copy, PartialEq)]
9pub enum MuscleGroup {
10    Arms,
11    Shoulders,
12    Chest,
13    Abdomen,
14    Back,
15    Legs,
16    Glutes,
17    Neck,
18}
19
20/// Muscle tone morph controller.
21#[derive(Debug, Clone)]
22pub struct MuscleToneMorph {
23    pub global_tone: f32,
24    pub group_overrides: Vec<(MuscleGroup, f32)>,
25    pub definition: f32,
26    pub morph_count: usize,
27    pub enabled: bool,
28}
29
30impl MuscleToneMorph {
31    pub fn new(morph_count: usize) -> Self {
32        MuscleToneMorph {
33            global_tone: 0.5,
34            group_overrides: Vec::new(),
35            definition: 0.5,
36            morph_count,
37            enabled: true,
38        }
39    }
40}
41
42/// Create a new muscle tone morph controller.
43pub fn new_muscle_tone_morph(morph_count: usize) -> MuscleToneMorph {
44    MuscleToneMorph::new(morph_count)
45}
46
47/// Set global muscle tone.
48pub fn mtm_set_tone(morph: &mut MuscleToneMorph, tone: f32) {
49    morph.global_tone = tone.clamp(0.0, 1.0);
50}
51
52/// Set definition (muscle separation visibility).
53pub fn mtm_set_definition(morph: &mut MuscleToneMorph, definition: f32) {
54    morph.definition = definition.clamp(0.0, 1.0);
55}
56
57/// Override tone for a specific muscle group.
58pub fn mtm_set_group_override(morph: &mut MuscleToneMorph, group: MuscleGroup, tone: f32) {
59    let clamped = tone.clamp(0.0, 1.0);
60    if let Some(entry) = morph.group_overrides.iter_mut().find(|(g, _)| *g == group) {
61        entry.1 = clamped;
62    } else {
63        morph.group_overrides.push((group, clamped));
64    }
65}
66
67/// Evaluate morph weights (stub: blend global tone with definition).
68pub fn mtm_evaluate(morph: &MuscleToneMorph) -> Vec<f32> {
69    /* Stub: weight = global_tone * definition */
70    if !morph.enabled || morph.morph_count == 0 {
71        return vec![];
72    }
73    let w = morph.global_tone * morph.definition;
74    vec![w; morph.morph_count]
75}
76
77/// Enable or disable.
78pub fn mtm_set_enabled(morph: &mut MuscleToneMorph, enabled: bool) {
79    morph.enabled = enabled;
80}
81
82/// Return number of group overrides.
83pub fn mtm_override_count(morph: &MuscleToneMorph) -> usize {
84    morph.group_overrides.len()
85}
86
87/// Serialize to JSON-like string.
88pub fn mtm_to_json(morph: &MuscleToneMorph) -> String {
89    format!(
90        r#"{{"global_tone":{},"definition":{},"morph_count":{},"enabled":{}}}"#,
91        morph.global_tone, morph.definition, morph.morph_count, morph.enabled
92    )
93}
94
95#[cfg(test)]
96mod tests {
97    use super::*;
98
99    #[test]
100    fn test_default_tone() {
101        let m = new_muscle_tone_morph(8);
102        assert!((m.global_tone - 0.5).abs() < 1e-6 /* default tone must be 0.5 */);
103    }
104
105    #[test]
106    fn test_set_tone_clamps() {
107        let mut m = new_muscle_tone_morph(4);
108        mtm_set_tone(&mut m, 1.5);
109        assert!((m.global_tone - 1.0).abs() < 1e-6 /* tone clamped to 1.0 */);
110    }
111
112    #[test]
113    fn test_set_definition() {
114        let mut m = new_muscle_tone_morph(4);
115        mtm_set_definition(&mut m, 0.8);
116        assert!((m.definition - 0.8).abs() < 1e-5 /* definition must be set */);
117    }
118
119    #[test]
120    fn test_group_override_added() {
121        let mut m = new_muscle_tone_morph(4);
122        mtm_set_group_override(&mut m, MuscleGroup::Arms, 0.9);
123        assert_eq!(
124            mtm_override_count(&m),
125            1 /* one override must be added */
126        );
127    }
128
129    #[test]
130    fn test_group_override_updated() {
131        let mut m = new_muscle_tone_morph(4);
132        mtm_set_group_override(&mut m, MuscleGroup::Arms, 0.5);
133        mtm_set_group_override(&mut m, MuscleGroup::Arms, 0.9);
134        assert_eq!(
135            mtm_override_count(&m),
136            1 /* duplicate group must not add new entry */
137        );
138    }
139
140    #[test]
141    fn test_evaluate_length() {
142        let m = new_muscle_tone_morph(5);
143        let out = mtm_evaluate(&m);
144        assert_eq!(out.len(), 5 /* output must match morph_count */);
145    }
146
147    #[test]
148    fn test_evaluate_disabled() {
149        let mut m = new_muscle_tone_morph(4);
150        mtm_set_enabled(&mut m, false);
151        assert!(mtm_evaluate(&m).is_empty() /* disabled must return empty */);
152    }
153
154    #[test]
155    fn test_to_json_has_fields() {
156        let m = new_muscle_tone_morph(4);
157        let j = mtm_to_json(&m);
158        assert!(j.contains("\"global_tone\"") /* JSON must have global_tone */);
159    }
160
161    #[test]
162    fn test_enabled_default() {
163        let m = new_muscle_tone_morph(4);
164        assert!(m.enabled /* must be enabled by default */);
165    }
166
167    #[test]
168    fn test_evaluate_product() {
169        let mut m = new_muscle_tone_morph(2);
170        mtm_set_tone(&mut m, 0.4);
171        mtm_set_definition(&mut m, 0.5);
172        let out = mtm_evaluate(&m);
173        assert!((out[0] - 0.2).abs() < 1e-5 /* weight must be tone * definition */);
174    }
175}