1#[allow(dead_code)]
8#[derive(Clone, Debug)]
9pub struct BoneWeight {
10 pub bone_index: u32,
11 pub weight: f32,
12}
13
14#[allow(dead_code)]
16pub struct WeightMapConfig {
17 pub width: u32,
18 pub height: u32,
19 pub bone_index: u32,
20 pub gamma: f32,
21}
22
23#[allow(dead_code)]
25pub struct WeightMapBuffer {
26 pub pixels: Vec<f32>,
27 pub width: u32,
28 pub height: u32,
29}
30
31#[allow(dead_code)]
33pub struct WeightMapStats {
34 pub min: f32,
35 pub max: f32,
36 pub avg: f32,
37}
38
39#[allow(dead_code)]
41pub type VertexWeights = Vec<Vec<BoneWeight>>;
42
43#[allow(dead_code)]
44pub fn default_weight_map_config(width: u32, height: u32) -> WeightMapConfig {
45 WeightMapConfig {
46 width,
47 height,
48 bone_index: 0,
49 gamma: 1.0,
50 }
51}
52
53#[allow(dead_code)]
54pub fn new_weight_map_buffer(width: u32, height: u32) -> WeightMapBuffer {
55 let count = (width * height) as usize;
56 WeightMapBuffer {
57 pixels: vec![0.0; count],
58 width,
59 height,
60 }
61}
62
63#[allow(dead_code)]
64pub fn set_weight_pixel(buffer: &mut WeightMapBuffer, x: u32, y: u32, value: f32) {
65 if x < buffer.width && y < buffer.height {
66 let idx = (y * buffer.width + x) as usize;
67 buffer.pixels[idx] = value;
68 }
69}
70
71#[allow(dead_code)]
72pub fn get_weight_pixel(buffer: &WeightMapBuffer, x: u32, y: u32) -> f32 {
73 if x < buffer.width && y < buffer.height {
74 let idx = (y * buffer.width + x) as usize;
75 buffer.pixels[idx]
76 } else {
77 0.0
78 }
79}
80
81#[allow(dead_code)]
83pub fn weight_map_from_vertices(
84 vertex_weights: &[Vec<BoneWeight>],
85 uvs: &[[f32; 2]],
86 bone_index: u32,
87 width: u32,
88 height: u32,
89) -> WeightMapBuffer {
90 let mut buffer = new_weight_map_buffer(width, height);
91 let count = vertex_weights.len().min(uvs.len());
92 for i in 0..count {
93 let w = vertex_weights[i]
94 .iter()
95 .find(|bw| bw.bone_index == bone_index)
96 .map_or(0.0, |bw| bw.weight);
97 let u = uvs[i][0].clamp(0.0, 1.0);
98 let v = uvs[i][1].clamp(0.0, 1.0);
99 let px = (u * (width as f32 - 1.0)).round() as u32;
100 let py = (v * (height as f32 - 1.0)).round() as u32;
101 set_weight_pixel(&mut buffer, px, py, w);
102 }
103 buffer
104}
105
106#[allow(dead_code)]
108pub fn encode_weight_map_ppm(buffer: &WeightMapBuffer) -> Vec<u8> {
109 let header = format!("P5\n{} {}\n255\n", buffer.width, buffer.height);
110 let mut out = header.into_bytes();
111 for &val in &buffer.pixels {
112 let byte = (val.clamp(0.0, 1.0) * 255.0).round() as u8;
113 out.push(byte);
114 }
115 out
116}
117
118#[allow(dead_code)]
120pub fn normalize_weights(vertex_weights: &mut [Vec<BoneWeight>]) {
121 for weights in vertex_weights.iter_mut() {
122 let sum: f32 = weights.iter().map(|bw| bw.weight).sum();
123 if sum > 1e-8 {
124 for bw in weights.iter_mut() {
125 bw.weight /= sum;
126 }
127 }
128 }
129}
130
131#[allow(dead_code)]
133pub fn top_n_weights(weights: &[BoneWeight], n: usize) -> Vec<BoneWeight> {
134 let mut sorted: Vec<BoneWeight> = weights.to_vec();
135 sorted.sort_by(|a, b| {
136 b.weight
137 .partial_cmp(&a.weight)
138 .unwrap_or(std::cmp::Ordering::Equal)
139 });
140 sorted.truncate(n);
141 sorted
142}
143
144#[allow(dead_code)]
146pub fn weight_map_to_csv(buffer: &WeightMapBuffer) -> String {
147 let mut out = String::from("x,y,weight\n");
148 for y in 0..buffer.height {
149 for x in 0..buffer.width {
150 let val = get_weight_pixel(buffer, x, y);
151 out.push_str(&format!("{},{},{:.6}\n", x, y, val));
152 }
153 }
154 out
155}
156
157#[allow(dead_code)]
159pub fn blend_weight_maps(a: &WeightMapBuffer, b: &WeightMapBuffer, t: f32) -> WeightMapBuffer {
160 let width = a.width;
161 let height = a.height;
162 let count = (width * height) as usize;
163 let t = t.clamp(0.0, 1.0);
164 let mut pixels = Vec::with_capacity(count);
165 for i in 0..count {
166 let va = a.pixels[i];
167 let vb = if i < b.pixels.len() { b.pixels[i] } else { 0.0 };
168 pixels.push(va * (1.0 - t) + vb * t);
169 }
170 WeightMapBuffer {
171 pixels,
172 width,
173 height,
174 }
175}
176
177#[allow(dead_code)]
179pub fn weight_map_pixel_count(buffer: &WeightMapBuffer) -> usize {
180 buffer.pixels.len()
181}
182
183#[allow(dead_code)]
185pub fn weight_map_stats(buffer: &WeightMapBuffer) -> WeightMapStats {
186 if buffer.pixels.is_empty() {
187 return WeightMapStats {
188 min: 0.0,
189 max: 0.0,
190 avg: 0.0,
191 };
192 }
193 let mut min = f32::MAX;
194 let mut max = f32::MIN;
195 let mut sum = 0.0f64;
196 for &v in &buffer.pixels {
197 if v < min {
198 min = v;
199 }
200 if v > max {
201 max = v;
202 }
203 sum += v as f64;
204 }
205 let avg = (sum / buffer.pixels.len() as f64) as f32;
206 WeightMapStats { min, max, avg }
207}
208
209#[allow(dead_code)]
211pub fn clear_weight_map(buffer: &mut WeightMapBuffer) {
212 for px in buffer.pixels.iter_mut() {
213 *px = 0.0;
214 }
215}
216
217#[cfg(test)]
218mod tests {
219 use super::*;
220
221 #[test]
222 fn test_default_config() {
223 let cfg = default_weight_map_config(256, 256);
224 assert_eq!(cfg.width, 256);
225 assert_eq!(cfg.height, 256);
226 assert_eq!(cfg.bone_index, 0);
227 assert!((cfg.gamma - 1.0).abs() < f32::EPSILON);
228 }
229
230 #[test]
231 fn test_new_buffer() {
232 let buf = new_weight_map_buffer(8, 8);
233 assert_eq!(buf.width, 8);
234 assert_eq!(buf.height, 8);
235 assert_eq!(buf.pixels.len(), 64);
236 }
237
238 #[test]
239 fn test_set_get_pixel() {
240 let mut buf = new_weight_map_buffer(4, 4);
241 set_weight_pixel(&mut buf, 2, 3, 0.75);
242 let v = get_weight_pixel(&buf, 2, 3);
243 assert!((v - 0.75).abs() < f32::EPSILON);
244 }
245
246 #[test]
247 fn test_get_pixel_out_of_bounds() {
248 let buf = new_weight_map_buffer(4, 4);
249 let v = get_weight_pixel(&buf, 10, 10);
250 assert!((v - 0.0).abs() < f32::EPSILON);
251 }
252
253 #[test]
254 fn test_set_pixel_out_of_bounds() {
255 let mut buf = new_weight_map_buffer(4, 4);
256 set_weight_pixel(&mut buf, 10, 10, 1.0);
257 for &px in &buf.pixels {
259 assert!((px - 0.0).abs() < f32::EPSILON);
260 }
261 }
262
263 #[test]
264 fn test_weight_map_from_vertices() {
265 let vw = vec![
266 vec![BoneWeight {
267 bone_index: 0,
268 weight: 1.0,
269 }],
270 vec![BoneWeight {
271 bone_index: 1,
272 weight: 0.5,
273 }],
274 ];
275 let uvs = [[0.0, 0.0], [0.5, 0.5]];
276 let buf = weight_map_from_vertices(&vw, &uvs, 0, 8, 8);
277 assert_eq!(buf.width, 8);
278 assert!((get_weight_pixel(&buf, 0, 0) - 1.0).abs() < f32::EPSILON);
280 }
281
282 #[test]
283 fn test_encode_ppm_starts_with_p5() {
284 let buf = new_weight_map_buffer(2, 2);
285 let ppm = encode_weight_map_ppm(&buf);
286 assert!(ppm.starts_with(b"P5"));
287 }
288
289 #[test]
290 fn test_encode_ppm_size() {
291 let buf = new_weight_map_buffer(4, 4);
292 let ppm = encode_weight_map_ppm(&buf);
293 let header = "P5\n4 4\n255\n".to_string();
294 assert_eq!(ppm.len(), header.len() + 16);
295 }
296
297 #[test]
298 fn test_normalize_weights() {
299 let mut vw = vec![vec![
300 BoneWeight {
301 bone_index: 0,
302 weight: 2.0,
303 },
304 BoneWeight {
305 bone_index: 1,
306 weight: 3.0,
307 },
308 ]];
309 normalize_weights(&mut vw);
310 let sum: f32 = vw[0].iter().map(|bw| bw.weight).sum();
311 assert!((sum - 1.0).abs() < 1e-6);
312 }
313
314 #[test]
315 fn test_normalize_weights_zero_sum() {
316 let mut vw = vec![vec![BoneWeight {
317 bone_index: 0,
318 weight: 0.0,
319 }]];
320 normalize_weights(&mut vw);
321 assert!((vw[0][0].weight - 0.0).abs() < f32::EPSILON);
323 }
324
325 #[test]
326 fn test_top_n_weights() {
327 let weights = vec![
328 BoneWeight {
329 bone_index: 0,
330 weight: 0.1,
331 },
332 BoneWeight {
333 bone_index: 1,
334 weight: 0.5,
335 },
336 BoneWeight {
337 bone_index: 2,
338 weight: 0.3,
339 },
340 BoneWeight {
341 bone_index: 3,
342 weight: 0.1,
343 },
344 ];
345 let top = top_n_weights(&weights, 2);
346 assert_eq!(top.len(), 2);
347 assert!((top[0].weight - 0.5).abs() < f32::EPSILON);
348 assert!((top[1].weight - 0.3).abs() < f32::EPSILON);
349 }
350
351 #[test]
352 fn test_weight_map_to_csv() {
353 let mut buf = new_weight_map_buffer(2, 2);
354 set_weight_pixel(&mut buf, 0, 0, 1.0);
355 let csv = weight_map_to_csv(&buf);
356 assert!(csv.starts_with("x,y,weight\n"));
357 assert!(csv.contains("0,0,1.000000"));
358 }
359
360 #[test]
361 fn test_blend_weight_maps() {
362 let mut a = new_weight_map_buffer(2, 2);
363 let mut b = new_weight_map_buffer(2, 2);
364 for px in a.pixels.iter_mut() {
365 *px = 0.0;
366 }
367 for px in b.pixels.iter_mut() {
368 *px = 1.0;
369 }
370 let blended = blend_weight_maps(&a, &b, 0.5);
371 for &px in &blended.pixels {
372 assert!((px - 0.5).abs() < f32::EPSILON);
373 }
374 }
375
376 #[test]
377 fn test_weight_map_pixel_count() {
378 let buf = new_weight_map_buffer(8, 4);
379 assert_eq!(weight_map_pixel_count(&buf), 32);
380 }
381
382 #[test]
383 fn test_weight_map_stats() {
384 let mut buf = new_weight_map_buffer(4, 4);
385 set_weight_pixel(&mut buf, 0, 0, 0.2);
386 set_weight_pixel(&mut buf, 1, 0, 0.8);
387 let stats = weight_map_stats(&buf);
388 assert!((stats.min - 0.0).abs() < f32::EPSILON);
389 assert!((stats.max - 0.8).abs() < f32::EPSILON);
390 assert!(stats.avg > 0.0);
391 }
392
393 #[test]
394 fn test_weight_map_stats_empty() {
395 let buf = WeightMapBuffer {
396 pixels: vec![],
397 width: 0,
398 height: 0,
399 };
400 let stats = weight_map_stats(&buf);
401 assert!((stats.min - 0.0).abs() < f32::EPSILON);
402 assert!((stats.max - 0.0).abs() < f32::EPSILON);
403 }
404
405 #[test]
406 fn test_clear_weight_map() {
407 let mut buf = new_weight_map_buffer(4, 4);
408 set_weight_pixel(&mut buf, 1, 1, 0.9);
409 clear_weight_map(&mut buf);
410 for &px in &buf.pixels {
411 assert!((px - 0.0).abs() < f32::EPSILON);
412 }
413 }
414}