Skip to main content

pfx_core/anim/
skin.rs

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}