1use super::AnimError;
2use super::math::{Mat4, normalize3, transform_point, transform_vector};
3
4#[derive(Clone, Debug, Default, PartialEq)]
5pub struct Skinned {
6 pub positions: Vec<[f32; 3]>,
7 pub normals: Vec<[f32; 3]>,
8 pub tangents: Vec<[f32; 4]>,
9}
10
11pub fn normalize_weights(weights: &mut [[f32; 4]]) {
12 for weight in weights {
13 let sum = weight[0] + weight[1] + weight[2] + weight[3];
14 if sum > 0.0 && sum.is_finite() {
15 for value in weight.iter_mut() {
16 *value /= sum;
17 }
18 } else {
19 *weight = [1.0, 0.0, 0.0, 0.0];
20 }
21 }
22}
23
24pub fn blend_matrix(palette: &[Mat4], joints: [u16; 4], weights: [f32; 4]) -> Mat4 {
25 let mut out = [[0.0; 4]; 4];
26 for (joint, weight) in joints.into_iter().zip(weights) {
27 let matrix = &palette[joint as usize];
28 for (column, target) in out.iter_mut().enumerate() {
29 for (row, value) in target.iter_mut().enumerate() {
30 *value += matrix[column][row] * weight;
31 }
32 }
33 }
34 out
35}
36
37pub fn check_skin(
38 vertices: usize,
39 joints: &[[u16; 4]],
40 weights: &[[f32; 4]],
41 palette: usize,
42) -> Result<(), AnimError> {
43 if joints.len() != vertices || weights.len() != vertices {
44 return Err(AnimError::Skin(format!(
45 "{} joint sets and {} weight sets for {vertices} vertices",
46 joints.len(),
47 weights.len()
48 )));
49 }
50 if let Some(joint) = joints
51 .iter()
52 .flatten()
53 .find(|joint| **joint as usize >= palette)
54 {
55 return Err(AnimError::Skin(format!(
56 "a vertex names joint {joint} of a palette of {palette}"
57 )));
58 }
59 if weights.iter().flatten().any(|weight| !weight.is_finite()) {
60 return Err(AnimError::Skin("a weight is not finite".into()));
61 }
62 Ok(())
63}
64
65pub fn skin(
66 positions: &[[f32; 3]],
67 normals: &[[f32; 3]],
68 tangents: &[[f32; 4]],
69 joints: &[[u16; 4]],
70 weights: &[[f32; 4]],
71 palette: &[Mat4],
72) -> Result<Skinned, AnimError> {
73 check_skin(positions.len(), joints, weights, palette.len())?;
74 if normals.len() != positions.len()
75 || (!tangents.is_empty() && tangents.len() != positions.len())
76 {
77 return Err(AnimError::Skin(
78 "normals and tangents need one entry per vertex".into(),
79 ));
80 }
81 let mut out = Skinned {
82 positions: Vec::with_capacity(positions.len()),
83 normals: Vec::with_capacity(positions.len()),
84 tangents: Vec::with_capacity(tangents.len()),
85 };
86 for index in 0..positions.len() {
87 let matrix = blend_matrix(palette, joints[index], weights[index]);
88 out.positions
89 .push(transform_point(&matrix, positions[index]));
90 out.normals
91 .push(normalize3(transform_vector(&matrix, normals[index])));
92 if let Some(tangent) = tangents.get(index) {
93 let along = normalize3(transform_vector(
94 &matrix,
95 [tangent[0], tangent[1], tangent[2]],
96 ));
97 out.tangents
98 .push([along[0], along[1], along[2], tangent[3]]);
99 }
100 }
101 Ok(out)
102}