Skip to main content

oxihuman_export/
morph_target_export.rs

1#![allow(dead_code)]
2// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
3// SPDX-License-Identifier: Apache-2.0
4
5/// A single morph target with vertex deltas.
6#[allow(dead_code)]
7#[derive(Debug, Clone)]
8pub struct MorphTarget {
9    pub name: String,
10    /// Flat delta positions [dx,dy,dz, ...].
11    pub deltas: Vec<f32>,
12}
13
14/// Export container for morph targets.
15#[allow(dead_code)]
16#[derive(Debug, Clone)]
17pub struct MorphTargetExport {
18    pub targets: Vec<MorphTarget>,
19}
20
21/// Create a morph target export.
22#[allow(dead_code)]
23pub fn export_morph_targets(names: &[&str], deltas: &[Vec<f32>]) -> MorphTargetExport {
24    let mut targets = Vec::new();
25    for (i, &name) in names.iter().enumerate() {
26        targets.push(MorphTarget {
27            name: name.to_string(),
28            deltas: deltas.get(i).cloned().unwrap_or_default(),
29        });
30    }
31    MorphTargetExport { targets }
32}
33
34/// Return the number of morph targets.
35#[allow(dead_code)]
36pub fn morph_target_count_export(exp: &MorphTargetExport) -> usize {
37    exp.targets.len()
38}
39
40/// Return the name of a morph target.
41#[allow(dead_code)]
42pub fn morph_target_name(exp: &MorphTargetExport, index: usize) -> Option<&str> {
43    exp.targets.get(index).map(|t| t.name.as_str())
44}
45
46/// Return the number of delta components for a target (len of deltas / 3).
47#[allow(dead_code)]
48pub fn morph_target_delta_count(exp: &MorphTargetExport, index: usize) -> usize {
49    exp.targets.get(index).map_or(0, |t| t.deltas.len() / 3)
50}
51
52/// Serialize to JSON.
53#[allow(dead_code)]
54pub fn morph_target_to_json(exp: &MorphTargetExport) -> String {
55    let mut s = String::from("{\"targets\":[");
56    for (i, t) in exp.targets.iter().enumerate() {
57        if i > 0 {
58            s.push(',');
59        }
60        s.push_str(&format!(
61            "{{\"name\":\"{}\",\"delta_verts\":{}}}",
62            t.name,
63            t.deltas.len() / 3
64        ));
65    }
66    s.push_str("]}");
67    s
68}
69
70/// Serialize to bytes (target count u32 LE, then per-target: name_len u32, name bytes, delta_count u32, float data).
71#[allow(dead_code)]
72pub fn morph_target_to_bytes(exp: &MorphTargetExport) -> Vec<u8> {
73    let mut buf = Vec::new();
74    buf.extend_from_slice(&(exp.targets.len() as u32).to_le_bytes());
75    for t in &exp.targets {
76        let name_bytes = t.name.as_bytes();
77        buf.extend_from_slice(&(name_bytes.len() as u32).to_le_bytes());
78        buf.extend_from_slice(name_bytes);
79        buf.extend_from_slice(&(t.deltas.len() as u32).to_le_bytes());
80        for &d in &t.deltas {
81            buf.extend_from_slice(&d.to_le_bytes());
82        }
83    }
84    buf
85}
86
87/// Return the total byte size.
88#[allow(dead_code)]
89pub fn morph_target_export_size(exp: &MorphTargetExport) -> usize {
90    morph_target_to_bytes(exp).len()
91}
92
93/// Validate that all targets have the same delta count.
94#[allow(dead_code)]
95pub fn validate_morph_target_export(exp: &MorphTargetExport) -> bool {
96    if exp.targets.is_empty() {
97        return true;
98    }
99    let first_len = exp.targets[0].deltas.len();
100    exp.targets.iter().all(|t| t.deltas.len() == first_len)
101}
102
103#[cfg(test)]
104mod tests {
105    use super::*;
106
107    fn sample() -> MorphTargetExport {
108        export_morph_targets(
109            &["smile", "blink"],
110            &[
111                vec![0.1, 0.0, 0.0, 0.0, 0.1, 0.0],
112                vec![0.0, -0.1, 0.0, 0.0, 0.0, 0.1],
113            ],
114        )
115    }
116
117    #[test]
118    fn test_count() {
119        assert_eq!(morph_target_count_export(&sample()), 2);
120    }
121
122    #[test]
123    fn test_name() {
124        assert_eq!(morph_target_name(&sample(), 0), Some("smile"));
125        assert_eq!(morph_target_name(&sample(), 1), Some("blink"));
126        assert_eq!(morph_target_name(&sample(), 5), None);
127    }
128
129    #[test]
130    fn test_delta_count() {
131        assert_eq!(morph_target_delta_count(&sample(), 0), 2);
132    }
133
134    #[test]
135    fn test_to_json() {
136        let j = morph_target_to_json(&sample());
137        assert!(j.contains("\"smile\""));
138        assert!(j.contains("\"blink\""));
139    }
140
141    #[test]
142    fn test_to_bytes() {
143        let b = morph_target_to_bytes(&sample());
144        let tc = u32::from_le_bytes([b[0], b[1], b[2], b[3]]);
145        assert_eq!(tc, 2);
146    }
147
148    #[test]
149    fn test_export_size() {
150        assert!(morph_target_export_size(&sample()) > 0);
151    }
152
153    #[test]
154    fn test_validate_ok() {
155        assert!(validate_morph_target_export(&sample()));
156    }
157
158    #[test]
159    fn test_validate_mismatch() {
160        let e = export_morph_targets(&["a", "b"], &[vec![1.0, 2.0, 3.0], vec![1.0]]);
161        assert!(!validate_morph_target_export(&e));
162    }
163
164    #[test]
165    fn test_empty() {
166        let e = export_morph_targets(&[], &[]);
167        assert_eq!(morph_target_count_export(&e), 0);
168        assert!(validate_morph_target_export(&e));
169    }
170
171    #[test]
172    fn test_delta_count_oob() {
173        assert_eq!(morph_target_delta_count(&sample(), 99), 0);
174    }
175}