oxihuman_morph/
fast_lbs.rs1#![allow(dead_code)]
4
5#[derive(Debug, Clone, Copy)]
9pub struct FastLbsRecord {
10 pub bones: [u8; 4],
11 pub weights: [f32; 4],
12}
13
14impl Default for FastLbsRecord {
15 fn default() -> Self {
16 FastLbsRecord {
17 bones: [0; 4],
18 weights: [0.0; 4],
19 }
20 }
21}
22
23#[derive(Debug, Clone)]
25pub struct FastLbs {
26 pub records: Vec<FastLbsRecord>,
27 pub bone_count: usize,
28}
29
30impl FastLbs {
31 pub fn new(vertex_count: usize, bone_count: usize) -> Self {
32 FastLbs {
33 records: vec![FastLbsRecord::default(); vertex_count],
34 bone_count,
35 }
36 }
37}
38
39pub fn new_fast_lbs(vertex_count: usize, bone_count: usize) -> FastLbs {
41 FastLbs::new(vertex_count, bone_count)
42}
43
44pub fn fast_lbs_set(lbs: &mut FastLbs, vertex: usize, record: FastLbsRecord) {
46 if vertex < lbs.records.len() {
47 lbs.records[vertex] = record;
48 }
49}
50
51pub fn fast_lbs_normalize(record: &mut FastLbsRecord) {
53 let sum: f32 = record.weights.iter().sum();
54 if sum > 1e-9 {
55 for w in &mut record.weights {
56 *w /= sum;
57 }
58 }
59}
60
61pub fn fast_lbs_transform(
63 lbs: &FastLbs,
64 vertex: usize,
65 source: [f32; 3],
66 _bone_matrices: &[[[f32; 4]; 4]],
67) -> [f32; 3] {
68 if vertex < lbs.records.len() {
70 source
71 } else {
72 [0.0; 3]
73 }
74}
75
76pub fn fast_lbs_vertex_count(lbs: &FastLbs) -> usize {
78 lbs.records.len()
79}
80
81pub fn fast_lbs_to_json(lbs: &FastLbs) -> String {
83 format!(
84 r#"{{"vertices":{},"bones":{}}}"#,
85 lbs.records.len(),
86 lbs.bone_count
87 )
88}
89
90pub fn fast_lbs_is_valid(lbs: &FastLbs) -> bool {
92 lbs.records.iter().all(|r| {
93 let s: f32 = r.weights.iter().sum();
94 s < 1e-9 || (s - 1.0).abs() < 1e-4
95 })
96}
97
98#[cfg(test)]
99mod tests {
100 use super::*;
101
102 #[test]
103 fn test_new_fast_lbs_vertex_count() {
104 let lbs = new_fast_lbs(16, 4);
105 assert_eq!(
106 fast_lbs_vertex_count(&lbs),
107 16, );
109 }
110
111 #[test]
112 fn test_initial_records_zero_weights() {
113 let lbs = new_fast_lbs(3, 2);
114 for r in &lbs.records {
115 let s: f32 = r.weights.iter().sum();
116 assert!((s).abs() < 1e-6 ,);
117 }
118 }
119
120 #[test]
121 fn test_initial_valid() {
122 let lbs = new_fast_lbs(4, 2);
123 assert!(fast_lbs_is_valid(&lbs), );
124 }
125
126 #[test]
127 fn test_set_record_updates() {
128 let mut lbs = new_fast_lbs(4, 2);
129 let r = FastLbsRecord {
130 bones: [1, 2, 0, 0],
131 weights: [0.5, 0.5, 0.0, 0.0],
132 };
133 fast_lbs_set(&mut lbs, 0, r);
134 assert!((lbs.records[0].weights[0] - 0.5).abs() < 1e-5, );
135 }
136
137 #[test]
138 fn test_set_out_of_bounds_ignored() {
139 let mut lbs = new_fast_lbs(2, 2);
140 fast_lbs_set(&mut lbs, 99, FastLbsRecord::default());
141 assert_eq!(
142 fast_lbs_vertex_count(&lbs),
143 2, );
145 }
146
147 #[test]
148 fn test_normalize_record() {
149 let mut r = FastLbsRecord {
150 bones: [0; 4],
151 weights: [2.0, 2.0, 0.0, 0.0],
152 };
153 fast_lbs_normalize(&mut r);
154 let s: f32 = r.weights.iter().sum();
155 assert!((s - 1.0).abs() < 1e-5, );
156 }
157
158 #[test]
159 fn test_normalize_zero_weights_unchanged() {
160 let mut r = FastLbsRecord::default();
161 fast_lbs_normalize(&mut r);
162 let s: f32 = r.weights.iter().sum();
163 assert!((s).abs() < 1e-6 ,);
164 }
165
166 #[test]
167 fn test_transform_returns_source() {
168 let lbs = new_fast_lbs(2, 2);
169 let pos = fast_lbs_transform(&lbs, 0, [1.0, 2.0, 3.0], &[]);
170 assert!((pos[0] - 1.0).abs() < 1e-5, );
171 }
172
173 #[test]
174 fn test_to_json_contains_vertices() {
175 let lbs = new_fast_lbs(5, 3);
176 let j = fast_lbs_to_json(&lbs);
177 assert!(j.contains("vertices") ,);
178 }
179
180 #[test]
181 fn test_bone_count_stored() {
182 let lbs = new_fast_lbs(4, 7);
183 assert_eq!(lbs.bone_count, 7 ,);
184 }
185}