Skip to main content

oxihuman_export/
deform_bind_export.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! Deformer binding export: records which vertices are bound to which deformer.
6
7#[allow(dead_code)]
8#[derive(Debug, Clone)]
9pub struct DeformBindEntry {
10    pub deformer_name: String,
11    pub vertex_indices: Vec<u32>,
12    pub weights: Vec<f32>,
13}
14
15#[allow(dead_code)]
16#[derive(Debug, Clone)]
17pub struct DeformBindExport {
18    pub bindings: Vec<DeformBindEntry>,
19}
20
21#[allow(dead_code)]
22pub fn new_deform_bind_export() -> DeformBindExport {
23    DeformBindExport {
24        bindings: Vec::new(),
25    }
26}
27
28#[allow(dead_code)]
29pub fn add_binding(exp: &mut DeformBindExport, name: &str, indices: Vec<u32>, weights: Vec<f32>) {
30    exp.bindings.push(DeformBindEntry {
31        deformer_name: name.to_string(),
32        vertex_indices: indices,
33        weights,
34    });
35}
36
37#[allow(dead_code)]
38pub fn binding_count(exp: &DeformBindExport) -> usize {
39    exp.bindings.len()
40}
41
42#[allow(dead_code)]
43pub fn find_binding<'a>(exp: &'a DeformBindExport, name: &str) -> Option<&'a DeformBindEntry> {
44    exp.bindings.iter().find(|b| b.deformer_name == name)
45}
46
47#[allow(dead_code)]
48pub fn total_bound_vertices(exp: &DeformBindExport) -> usize {
49    exp.bindings.iter().map(|b| b.vertex_indices.len()).sum()
50}
51
52#[allow(dead_code)]
53pub fn binding_avg_weight(entry: &DeformBindEntry) -> f32 {
54    if entry.weights.is_empty() {
55        return 0.0;
56    }
57    entry.weights.iter().sum::<f32>() / entry.weights.len() as f32
58}
59
60#[allow(dead_code)]
61pub fn binding_is_valid(entry: &DeformBindEntry) -> bool {
62    entry.vertex_indices.len() == entry.weights.len()
63        && entry.weights.iter().all(|&w| (0.0..=1.0).contains(&w))
64}
65
66#[allow(dead_code)]
67pub fn deform_bind_to_json(exp: &DeformBindExport) -> String {
68    format!(
69        "{{\"binding_count\":{},\"total_vertices\":{}}}",
70        binding_count(exp),
71        total_bound_vertices(exp)
72    )
73}
74
75#[allow(dead_code)]
76pub fn normalize_binding_weights(entry: &mut DeformBindEntry) {
77    let sum: f32 = entry.weights.iter().sum();
78    if sum > 0.0 {
79        for w in &mut entry.weights {
80            *w /= sum;
81        }
82    }
83}
84
85#[cfg(test)]
86mod tests {
87    use super::*;
88
89    #[test]
90    fn test_empty() {
91        let exp = new_deform_bind_export();
92        assert_eq!(binding_count(&exp), 0);
93    }
94
95    #[test]
96    fn test_add_binding() {
97        let mut exp = new_deform_bind_export();
98        add_binding(&mut exp, "lattice", vec![0, 1, 2], vec![1.0, 0.8, 0.6]);
99        assert_eq!(binding_count(&exp), 1);
100    }
101
102    #[test]
103    fn test_find_binding() {
104        let mut exp = new_deform_bind_export();
105        add_binding(&mut exp, "curve", vec![5], vec![1.0]);
106        assert!(find_binding(&exp, "curve").is_some());
107    }
108
109    #[test]
110    fn test_total_bound_vertices() {
111        let mut exp = new_deform_bind_export();
112        add_binding(&mut exp, "a", vec![0, 1], vec![1.0, 1.0]);
113        add_binding(&mut exp, "b", vec![2, 3, 4], vec![0.5, 0.5, 0.5]);
114        assert_eq!(total_bound_vertices(&exp), 5);
115    }
116
117    #[test]
118    fn test_avg_weight() {
119        let entry = DeformBindEntry {
120            deformer_name: "x".to_string(),
121            vertex_indices: vec![0, 1],
122            weights: vec![1.0, 0.0],
123        };
124        assert!((binding_avg_weight(&entry) - 0.5).abs() < 1e-5);
125    }
126
127    #[test]
128    fn test_binding_is_valid() {
129        let entry = DeformBindEntry {
130            deformer_name: "x".to_string(),
131            vertex_indices: vec![0, 1],
132            weights: vec![0.5, 0.5],
133        };
134        assert!(binding_is_valid(&entry));
135    }
136
137    #[test]
138    fn test_binding_invalid_mismatch() {
139        let entry = DeformBindEntry {
140            deformer_name: "x".to_string(),
141            vertex_indices: vec![0, 1],
142            weights: vec![0.5],
143        };
144        assert!(!binding_is_valid(&entry));
145    }
146
147    #[test]
148    fn test_normalize() {
149        let mut entry = DeformBindEntry {
150            deformer_name: "x".to_string(),
151            vertex_indices: vec![0, 1],
152            weights: vec![2.0, 2.0],
153        };
154        normalize_binding_weights(&mut entry);
155        assert!((entry.weights.iter().sum::<f32>() - 1.0).abs() < 1e-5);
156    }
157
158    #[test]
159    fn test_json_output() {
160        let exp = new_deform_bind_export();
161        let j = deform_bind_to_json(&exp);
162        assert!(j.contains("binding_count"));
163    }
164
165    #[test]
166    fn test_find_missing() {
167        let exp = new_deform_bind_export();
168        assert!(find_binding(&exp, "missing").is_none());
169    }
170}