oxihuman_morph/
linear_blend_skin.rs1#![allow(dead_code)]
4
5#[derive(Debug, Clone, Copy)]
9pub struct SkinInfluence {
10 pub bone_index: usize,
11 pub weight: f32,
12}
13
14#[derive(Debug, Clone)]
16pub struct LbsVertex {
17 pub influences: Vec<SkinInfluence>,
18}
19
20impl LbsVertex {
21 pub fn new() -> Self {
22 LbsVertex {
23 influences: Vec::new(),
24 }
25 }
26}
27
28impl Default for LbsVertex {
29 fn default() -> Self {
30 LbsVertex::new()
31 }
32}
33
34#[derive(Debug, Clone)]
36pub struct LinearBlendSkin {
37 pub vertices: Vec<LbsVertex>,
38 pub bone_count: usize,
39}
40
41impl LinearBlendSkin {
42 pub fn new(vertex_count: usize, bone_count: usize) -> Self {
43 LinearBlendSkin {
44 vertices: (0..vertex_count).map(|_| LbsVertex::default()).collect(),
45 bone_count,
46 }
47 }
48}
49
50pub fn new_lbs(vertex_count: usize, bone_count: usize) -> LinearBlendSkin {
52 LinearBlendSkin::new(vertex_count, bone_count)
53}
54
55pub fn lbs_add_influence(lbs: &mut LinearBlendSkin, vertex: usize, bone: usize, weight: f32) {
57 if vertex < lbs.vertices.len() && bone < lbs.bone_count {
58 lbs.vertices[vertex].influences.push(SkinInfluence {
59 bone_index: bone,
60 weight,
61 });
62 }
63}
64
65pub fn lbs_normalize(lbs: &mut LinearBlendSkin) {
67 for v in &mut lbs.vertices {
68 let sum: f32 = v.influences.iter().map(|i| i.weight).sum();
69 if sum > 1e-9 {
70 for inf in &mut v.influences {
71 inf.weight /= sum;
72 }
73 }
74 }
75}
76
77pub fn lbs_vertex_count(lbs: &LinearBlendSkin) -> usize {
79 lbs.vertices.len()
80}
81
82pub fn lbs_influence_count(lbs: &LinearBlendSkin, vertex: usize) -> usize {
84 if vertex < lbs.vertices.len() {
85 lbs.vertices[vertex].influences.len()
86 } else {
87 0
88 }
89}
90
91pub fn lbs_to_json(lbs: &LinearBlendSkin) -> String {
93 format!(
94 r#"{{"vertices":{},"bones":{}}}"#,
95 lbs.vertices.len(),
96 lbs.bone_count
97 )
98}
99
100pub fn lbs_is_normalized(lbs: &LinearBlendSkin) -> bool {
102 lbs.vertices
103 .iter()
104 .filter(|v| !v.influences.is_empty())
105 .all(|v| {
106 let s: f32 = v.influences.iter().map(|i| i.weight).sum();
107 (s - 1.0).abs() < 1e-4
108 })
109}
110
111#[cfg(test)]
112mod tests {
113 use super::*;
114
115 #[test]
116 fn test_new_lbs_vertex_count() {
117 let lbs = new_lbs(20, 5);
118 assert_eq!(
119 lbs_vertex_count(&lbs),
120 20, );
122 }
123
124 #[test]
125 fn test_add_influence_increases_count() {
126 let mut lbs = new_lbs(5, 3);
127 lbs_add_influence(&mut lbs, 0, 0, 0.5);
128 lbs_add_influence(&mut lbs, 0, 1, 0.5);
129 assert_eq!(
130 lbs_influence_count(&lbs, 0),
131 2, );
133 }
134
135 #[test]
136 fn test_normalize_makes_sum_one() {
137 let mut lbs = new_lbs(2, 3);
138 lbs_add_influence(&mut lbs, 0, 0, 2.0);
139 lbs_add_influence(&mut lbs, 0, 1, 2.0);
140 lbs_normalize(&mut lbs);
141 assert!(lbs_is_normalized(&lbs), );
142 }
143
144 #[test]
145 fn test_add_out_of_bounds_ignored() {
146 let mut lbs = new_lbs(2, 3);
147 lbs_add_influence(&mut lbs, 99, 0, 1.0);
148 assert_eq!(
149 lbs_influence_count(&lbs, 0),
150 0, );
152 }
153
154 #[test]
155 fn test_add_invalid_bone_ignored() {
156 let mut lbs = new_lbs(2, 3);
157 lbs_add_influence(&mut lbs, 0, 99, 1.0);
158 assert_eq!(
159 lbs_influence_count(&lbs, 0),
160 0, );
162 }
163
164 #[test]
165 fn test_to_json_contains_bones() {
166 let lbs = new_lbs(4, 6);
167 let j = lbs_to_json(&lbs);
168 assert!(j.contains("bones") ,);
169 }
170
171 #[test]
172 fn test_empty_vertices_are_normalized() {
173 let lbs = new_lbs(3, 2);
174 assert!(lbs_is_normalized(&lbs), );
175 }
176
177 #[test]
178 fn test_influence_count_out_of_bounds() {
179 let lbs = new_lbs(2, 3);
180 assert_eq!(
181 lbs_influence_count(&lbs, 99),
182 0, );
184 }
185
186 #[test]
187 fn test_bone_count_stored() {
188 let lbs = new_lbs(5, 8);
189 assert_eq!(lbs.bone_count, 8 ,);
190 }
191
192 #[test]
193 fn test_single_influence_is_normalized() {
194 let mut lbs = new_lbs(1, 1);
195 lbs_add_influence(&mut lbs, 0, 0, 1.0);
196 assert!(lbs_is_normalized(&lbs), );
197 }
198}