Skip to main content

oxihuman_morph/
hip_tilt_morph.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! Hip tilt / pelvic obliquity morph.
6
7/// Hip tilt configuration.
8#[derive(Debug, Clone)]
9pub struct HipTiltMorphConfig {
10    pub lateral_tilt: f32,
11    pub anterior_tilt: f32,
12    pub pelvic_rotation: f32,
13}
14
15impl Default for HipTiltMorphConfig {
16    fn default() -> Self {
17        Self {
18            lateral_tilt: 0.0,
19            anterior_tilt: 0.0,
20            pelvic_rotation: 0.0,
21        }
22    }
23}
24
25/// Hip tilt morph state.
26#[derive(Debug, Clone)]
27pub struct HipTiltMorph {
28    pub config: HipTiltMorphConfig,
29    pub intensity: f32,
30    pub enabled: bool,
31}
32
33impl HipTiltMorph {
34    pub fn new() -> Self {
35        Self {
36            config: HipTiltMorphConfig::default(),
37            intensity: 0.0,
38            enabled: true,
39        }
40    }
41}
42
43impl Default for HipTiltMorph {
44    fn default() -> Self {
45        Self::new()
46    }
47}
48
49/// Create a new HipTiltMorph.
50pub fn new_hip_tilt_morph() -> HipTiltMorph {
51    HipTiltMorph::new()
52}
53
54/// Set lateral tilt factor (-1.0 left, 1.0 right).
55pub fn hip_tilt_set_lateral(morph: &mut HipTiltMorph, v: f32) {
56    morph.config.lateral_tilt = v.clamp(-1.0, 1.0);
57}
58
59/// Set anterior tilt factor.
60pub fn hip_tilt_set_anterior(morph: &mut HipTiltMorph, v: f32) {
61    morph.config.anterior_tilt = v.clamp(-1.0, 1.0);
62}
63
64/// Set pelvic rotation factor.
65pub fn hip_tilt_set_rotation(morph: &mut HipTiltMorph, v: f32) {
66    morph.config.pelvic_rotation = v.clamp(-1.0, 1.0);
67}
68
69/// Compute total tilt magnitude.
70pub fn hip_tilt_magnitude(morph: &HipTiltMorph) -> f32 {
71    let l = morph.config.lateral_tilt;
72    let a = morph.config.anterior_tilt;
73    ((l * l + a * a).sqrt() * morph.intensity).min(1.0)
74}
75
76/// Serialize to JSON.
77pub fn hip_tilt_to_json(morph: &HipTiltMorph) -> String {
78    format!(
79        r#"{{"intensity":{},"lateral_tilt":{},"anterior_tilt":{},"pelvic_rotation":{}}}"#,
80        morph.intensity,
81        morph.config.lateral_tilt,
82        morph.config.anterior_tilt,
83        morph.config.pelvic_rotation,
84    )
85}
86
87#[cfg(test)]
88mod tests {
89    use super::*;
90
91    #[test]
92    fn test_new() {
93        let m = new_hip_tilt_morph();
94        assert!((m.config.lateral_tilt - 0.0).abs() < 1e-6 /* default zero */);
95    }
96
97    #[test]
98    fn test_lateral_clamp() {
99        let mut m = new_hip_tilt_morph();
100        hip_tilt_set_lateral(&mut m, 5.0);
101        assert!((m.config.lateral_tilt - 1.0).abs() < 1e-6 /* clamped */);
102    }
103
104    #[test]
105    fn test_anterior_negative() {
106        let mut m = new_hip_tilt_morph();
107        hip_tilt_set_anterior(&mut m, -0.4);
108        assert!((m.config.anterior_tilt - (-0.4)).abs() < 1e-6 /* negative stored */);
109    }
110
111    #[test]
112    fn test_rotation() {
113        let mut m = new_hip_tilt_morph();
114        hip_tilt_set_rotation(&mut m, 0.5);
115        assert!((m.config.pelvic_rotation - 0.5).abs() < 1e-6 /* stored */);
116    }
117
118    #[test]
119    fn test_magnitude_zero() {
120        let m = new_hip_tilt_morph();
121        assert!((hip_tilt_magnitude(&m) - 0.0).abs() < 1e-6 /* zero */);
122    }
123
124    #[test]
125    fn test_magnitude_nonzero() {
126        let mut m = new_hip_tilt_morph();
127        hip_tilt_set_lateral(&mut m, 1.0);
128        m.intensity = 1.0;
129        assert!(hip_tilt_magnitude(&m) > 0.0 /* positive */);
130    }
131
132    #[test]
133    fn test_json_key() {
134        let m = new_hip_tilt_morph();
135        let j = hip_tilt_to_json(&m);
136        assert!(j.contains("lateral_tilt") /* key */);
137    }
138
139    #[test]
140    fn test_default_enabled() {
141        let m = HipTiltMorph::default();
142        assert!(m.enabled /* enabled */);
143    }
144
145    #[test]
146    fn test_clone() {
147        let m = new_hip_tilt_morph();
148        let c = m.clone();
149        assert!((c.intensity - m.intensity).abs() < 1e-6 /* equal */);
150    }
151}