Skip to main content

oxihuman_export/
key_driver_export.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! Export key-driven shape / property driver data.
6
7/// A driver curve mapping an input value to an output value.
8#[allow(dead_code)]
9#[derive(Debug, Clone)]
10pub struct DriverCurve {
11    pub input_min: f32,
12    pub input_max: f32,
13    pub output_min: f32,
14    pub output_max: f32,
15    pub clamp_output: bool,
16}
17
18impl Default for DriverCurve {
19    fn default() -> Self {
20        Self {
21            input_min: 0.0,
22            input_max: 1.0,
23            output_min: 0.0,
24            output_max: 1.0,
25            clamp_output: true,
26        }
27    }
28}
29
30/// A single key driver connecting a source property to a target.
31#[allow(dead_code)]
32#[derive(Debug, Clone)]
33pub struct KeyDriver {
34    pub name: String,
35    pub source_bone: String,
36    pub source_property: String,
37    pub target_shape: String,
38    pub curve: DriverCurve,
39}
40
41/// Key driver export collection.
42#[allow(dead_code)]
43#[derive(Debug, Clone, Default)]
44pub struct KeyDriverExport {
45    pub drivers: Vec<KeyDriver>,
46}
47
48/// Create a new export.
49#[allow(dead_code)]
50pub fn new_key_driver_export() -> KeyDriverExport {
51    KeyDriverExport::default()
52}
53
54/// Add a driver.
55#[allow(dead_code)]
56pub fn add_driver(export: &mut KeyDriverExport, driver: KeyDriver) {
57    export.drivers.push(driver);
58}
59
60/// Evaluate a driver curve for a given input.
61#[allow(dead_code)]
62pub fn evaluate_curve(curve: &DriverCurve, input: f32) -> f32 {
63    let range_in = curve.input_max - curve.input_min;
64    let t = if range_in.abs() < 1e-8 {
65        0.0
66    } else {
67        (input - curve.input_min) / range_in
68    };
69    let out = curve.output_min + t * (curve.output_max - curve.output_min);
70    if curve.clamp_output {
71        out.clamp(
72            curve.output_min.min(curve.output_max),
73            curve.output_min.max(curve.output_max),
74        )
75    } else {
76        out
77    }
78}
79
80/// Evaluate a named driver at the given input.
81#[allow(dead_code)]
82pub fn evaluate_driver(export: &KeyDriverExport, name: &str, input: f32) -> Option<f32> {
83    export
84        .drivers
85        .iter()
86        .find(|d| d.name == name)
87        .map(|d| evaluate_curve(&d.curve, input))
88}
89
90/// Find all drivers targeting a given shape.
91#[allow(dead_code)]
92pub fn drivers_for_shape<'a>(export: &'a KeyDriverExport, shape: &str) -> Vec<&'a KeyDriver> {
93    export
94        .drivers
95        .iter()
96        .filter(|d| d.target_shape == shape)
97        .collect()
98}
99
100/// Count drivers.
101#[allow(dead_code)]
102pub fn driver_count(export: &KeyDriverExport) -> usize {
103    export.drivers.len()
104}
105
106/// Serialise curve to flat buffer.
107#[allow(dead_code)]
108pub fn serialise_curve(curve: &DriverCurve) -> Vec<f32> {
109    vec![
110        curve.input_min,
111        curve.input_max,
112        curve.output_min,
113        curve.output_max,
114    ]
115}
116
117/// Check all drivers have distinct names.
118#[allow(dead_code)]
119pub fn names_unique(export: &KeyDriverExport) -> bool {
120    let mut seen = std::collections::HashSet::new();
121    export.drivers.iter().all(|d| seen.insert(d.name.clone()))
122}
123
124#[cfg(test)]
125mod tests {
126    use super::*;
127
128    fn sample_driver(name: &str, shape: &str) -> KeyDriver {
129        KeyDriver {
130            name: name.to_string(),
131            source_bone: "arm".to_string(),
132            source_property: "rotation_x".to_string(),
133            target_shape: shape.to_string(),
134            curve: DriverCurve::default(),
135        }
136    }
137
138    #[test]
139    fn test_evaluate_curve_midpoint() {
140        let c = DriverCurve::default();
141        assert!((evaluate_curve(&c, 0.5) - 0.5).abs() < 1e-6);
142    }
143
144    #[test]
145    fn test_evaluate_curve_min() {
146        let c = DriverCurve::default();
147        assert!((evaluate_curve(&c, 0.0) - 0.0).abs() < 1e-6);
148    }
149
150    #[test]
151    fn test_evaluate_curve_max() {
152        let c = DriverCurve::default();
153        assert!((evaluate_curve(&c, 1.0) - 1.0).abs() < 1e-6);
154    }
155
156    #[test]
157    fn test_evaluate_curve_clamp() {
158        let c = DriverCurve::default();
159        let out = evaluate_curve(&c, 2.0);
160        assert!((0.0..=1.0).contains(&out));
161    }
162
163    #[test]
164    fn test_add_driver() {
165        let mut e = new_key_driver_export();
166        add_driver(&mut e, sample_driver("d1", "smile"));
167        assert_eq!(driver_count(&e), 1);
168    }
169
170    #[test]
171    fn test_evaluate_driver_found() {
172        let mut e = new_key_driver_export();
173        add_driver(&mut e, sample_driver("brow_up", "brow"));
174        let v = evaluate_driver(&e, "brow_up", 0.5);
175        assert!(v.is_some());
176    }
177
178    #[test]
179    fn test_evaluate_driver_not_found() {
180        let e = new_key_driver_export();
181        assert!(evaluate_driver(&e, "missing", 0.5).is_none());
182    }
183
184    #[test]
185    fn test_drivers_for_shape() {
186        let mut e = new_key_driver_export();
187        add_driver(&mut e, sample_driver("d1", "smile"));
188        add_driver(&mut e, sample_driver("d2", "frown"));
189        let r = drivers_for_shape(&e, "smile");
190        assert_eq!(r.len(), 1);
191    }
192
193    #[test]
194    fn test_serialise_curve_length() {
195        assert_eq!(serialise_curve(&DriverCurve::default()).len(), 4);
196    }
197
198    #[test]
199    fn test_names_unique() {
200        let mut e = new_key_driver_export();
201        add_driver(&mut e, sample_driver("d1", "s"));
202        add_driver(&mut e, sample_driver("d2", "s"));
203        assert!(names_unique(&e));
204    }
205}