Skip to main content

oxihuman_morph/
gaze_driven_shape.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! Gaze direction driven morph stub.
6
7/// Gaze direction input in spherical coordinates.
8#[derive(Debug, Clone, Copy)]
9pub struct GazeDirection {
10    pub yaw: f32,
11    pub pitch: f32,
12}
13
14impl Default for GazeDirection {
15    fn default() -> Self {
16        GazeDirection {
17            yaw: 0.0,
18            pitch: 0.0,
19        }
20    }
21}
22
23/// Gaze-driven shape controller.
24#[derive(Debug, Clone)]
25pub struct GazeDrivenShape {
26    pub direction: GazeDirection,
27    pub morph_count: usize,
28    pub yaw_gain: f32,
29    pub pitch_gain: f32,
30    pub enabled: bool,
31}
32
33impl GazeDrivenShape {
34    pub fn new(morph_count: usize) -> Self {
35        GazeDrivenShape {
36            direction: GazeDirection::default(),
37            morph_count,
38            yaw_gain: 1.0,
39            pitch_gain: 1.0,
40            enabled: true,
41        }
42    }
43}
44
45/// Create a new gaze-driven shape controller.
46pub fn new_gaze_driven_shape(morph_count: usize) -> GazeDrivenShape {
47    GazeDrivenShape::new(morph_count)
48}
49
50/// Update gaze direction.
51pub fn gds_set_direction(gds: &mut GazeDrivenShape, direction: GazeDirection) {
52    gds.direction = direction;
53}
54
55/// Evaluate morph weights from current gaze (stub: uniform distribution).
56pub fn gds_evaluate(gds: &GazeDrivenShape) -> Vec<f32> {
57    /* Stub: returns zeroed weights */
58    vec![0.0; gds.morph_count]
59}
60
61/// Set yaw and pitch gains.
62pub fn gds_set_gains(gds: &mut GazeDrivenShape, yaw_gain: f32, pitch_gain: f32) {
63    gds.yaw_gain = yaw_gain;
64    gds.pitch_gain = pitch_gain;
65}
66
67/// Enable or disable.
68pub fn gds_set_enabled(gds: &mut GazeDrivenShape, enabled: bool) {
69    gds.enabled = enabled;
70}
71
72/// Serialize to JSON-like string.
73pub fn gds_to_json(gds: &GazeDrivenShape) -> String {
74    format!(
75        r#"{{"morph_count":{},"yaw":{:.4},"pitch":{:.4},"enabled":{}}}"#,
76        gds.morph_count, gds.direction.yaw, gds.direction.pitch, gds.enabled
77    )
78}
79
80#[cfg(test)]
81mod tests {
82    use super::*;
83
84    #[test]
85    fn test_new_morph_count() {
86        let g = new_gaze_driven_shape(4);
87        assert_eq!(g.morph_count, 4 /* morph count must match */,);
88    }
89
90    #[test]
91    fn test_default_direction_zero() {
92        let g = new_gaze_driven_shape(4);
93        assert!((g.direction.yaw).abs() < 1e-6, /* default yaw must be zero */);
94        assert!((g.direction.pitch).abs() < 1e-6, /* default pitch must be zero */);
95    }
96
97    #[test]
98    fn test_set_direction() {
99        let mut g = new_gaze_driven_shape(4);
100        gds_set_direction(
101            &mut g,
102            GazeDirection {
103                yaw: 0.3,
104                pitch: -0.1,
105            },
106        );
107        assert!((g.direction.yaw - 0.3).abs() < 1e-5, /* yaw must be set */);
108    }
109
110    #[test]
111    fn test_evaluate_length() {
112        let g = new_gaze_driven_shape(6);
113        let out = gds_evaluate(&g);
114        assert_eq!(out.len(), 6 /* output length must match morph_count */,);
115    }
116
117    #[test]
118    fn test_evaluate_zeroed() {
119        let g = new_gaze_driven_shape(3);
120        let out = gds_evaluate(&g);
121        assert!(out.iter().all(|&v| v.abs() < 1e-6), /* stub must return zeros */);
122    }
123
124    #[test]
125    fn test_set_gains() {
126        let mut g = new_gaze_driven_shape(2);
127        gds_set_gains(&mut g, 0.5, 2.0);
128        assert!((g.yaw_gain - 0.5).abs() < 1e-5, /* yaw gain must be set */);
129        assert!((g.pitch_gain - 2.0).abs() < 1e-5, /* pitch gain must be set */);
130    }
131
132    #[test]
133    fn test_set_enabled() {
134        let mut g = new_gaze_driven_shape(2);
135        gds_set_enabled(&mut g, false);
136        assert!(!g.enabled /* must be disabled */,);
137    }
138
139    #[test]
140    fn test_to_json_contains_morph_count() {
141        let g = new_gaze_driven_shape(5);
142        let j = gds_to_json(&g);
143        assert!(j.contains("\"morph_count\""), /* json must contain morph_count */);
144    }
145
146    #[test]
147    fn test_enabled_default() {
148        let g = new_gaze_driven_shape(1);
149        assert!(g.enabled /* must be enabled by default */,);
150    }
151
152    #[test]
153    fn test_default_gains() {
154        let g = new_gaze_driven_shape(1);
155        assert!((g.yaw_gain - 1.0).abs() < 1e-5, /* default yaw gain must be 1.0 */);
156        assert!((g.pitch_gain - 1.0).abs() < 1e-5, /* default pitch gain must be 1.0 */);
157    }
158}