blender_mesh/
face_tangents.rs

1use crate::vertex_attributes::MultiIndexedVertexAttributes;
2use crate::BlenderMesh;
3
4/// Indicates an error while calculating the tangents for a mesh's verticies
5#[derive(Debug, Fail)]
6pub enum TangentError {
7    #[fail(display = "Cannot calculate vertex tangents for a mesh with no uvs")]
8    NoVertexUvs,
9}
10
11impl BlenderMesh {
12    /// Calculate the tangent for each face in the mesh - useful for normal mapping where you'll
13    /// typically want to do lighting calculations in tangent space.
14    ///
15    /// We look at the first, second and third vertex position and uv coordinate in the
16    /// face in order to calculate the tangent.
17    ///
18    /// This is useful for normal mapping.
19    ///
20    /// We'll push tangents to `.face_tangents`
21    ///
22    /// Then later in when combining indices we'll use these `.face_tangents` in order to
23    /// generate `per_vertex_tangents`
24    pub(crate) fn calculate_face_tangents(&self) -> Result<Vec<f32>, TangentError> {
25        let multi = &self.multi_indexed_vertex_attributes;
26
27        let MultiIndexedVertexAttributes {
28            vertices_in_each_face,
29            positions,
30            uvs,
31            ..
32        } = multi;
33
34        if uvs.is_none() {
35            return Err(TangentError::NoVertexUvs)?;
36        }
37        let uvs = uvs.as_ref().unwrap();
38
39        let mut total_indices_processed = 0;
40
41        let mut face_tangents = vec![];
42
43        // Iterate over each face and calculate the tangent for that face.
44        for vertices_in_face in vertices_in_each_face.iter() {
45            let vertices_in_face = *vertices_in_face;
46
47            let idx = total_indices_processed as usize;
48
49            // Get the first three vertex indices for this face
50            let pos_idx_0 = positions.indices[idx];
51            let pos_idx_1 = positions.indices[idx + 1];
52            let pos_idx_2 = positions.indices[idx + 2];
53
54            // Get the three UV indices for this face
55            let uv_idx_0 = uvs.indices[idx];
56            let uv_idx_1 = uvs.indices[idx + 1];
57            let uv_idx_2 = uvs.indices[idx + 2];
58
59            let pos0 = positions.attribute.data_at_idx(pos_idx_0);
60            let pos1 = positions.attribute.data_at_idx(pos_idx_1);
61            let pos2 = positions.attribute.data_at_idx(pos_idx_2);
62
63            let uv0 = uvs.attribute.data_at_idx(uv_idx_0);
64            let uv1 = uvs.attribute.data_at_idx(uv_idx_1);
65            let uv2 = uvs.attribute.data_at_idx(uv_idx_2);
66
67            let edge1 = (pos1[0] - pos0[0], pos1[1] - pos0[1], pos1[2] - pos0[2]);
68            let edge2 = (pos2[0] - pos1[0], pos2[1] - pos1[1], pos2[2] - pos1[2]);
69
70            let delta_uv1 = (uv1[0] - uv0[0], uv1[1] - uv0[1]);
71            let delta_uv2 = (uv2[0] - uv1[0], uv2[1] - uv1[1]);
72
73            let f = 1.0 / ((delta_uv1.0 * delta_uv2.1) - (delta_uv2.0 * delta_uv1.1));
74
75            let tangent_x = f * ((delta_uv2.1 * edge1.0) - (delta_uv1.1 * edge2.0));
76            let tangent_y = f * ((delta_uv2.1 * edge1.1) - (delta_uv1.1 * edge2.1));
77            let tangent_z = f * ((delta_uv2.1 * edge1.2) - (delta_uv1.1 * edge2.2));
78
79            face_tangents.push(tangent_x);
80            face_tangents.push(tangent_y);
81            face_tangents.push(tangent_z);
82
83            total_indices_processed += vertices_in_face as u16;
84        }
85
86        Ok(face_tangents)
87    }
88}
89
90/// Given a face idx, get the corresponding tangent
91pub(crate) fn face_tangent_at_idx(face_tangents: &[f32], face_idx: usize) -> (f32, f32, f32) {
92    (
93        face_tangents[face_idx * 3],
94        face_tangents[face_idx * 3 + 1],
95        face_tangents[face_idx * 3 + 2],
96    )
97}
98
99// Numbers in these tests were not verified by hand.
100// Instead, we took this common tangent calculation formula wrote tests, and verified
101// that the rendered models looked visually correct (meaning that our test values are also correct).
102#[cfg(test)]
103mod tests {
104    use super::*;
105    use crate::concat_vecs;
106    use crate::test_utils::*;
107
108    /// Ensure that a mesh with no uvs returns TangentError::NoVertexUvs
109    #[test]
110    fn no_vertex_uvs() {
111        let mesh: BlenderMesh = BlenderMesh::default();
112
113        match mesh.calculate_face_tangents() {
114            Ok(_) => unreachable!(),
115            Err(TangentError::NoVertexUvs) => {}
116        }
117    }
118
119    /// Properly calculates tangents for a mesh that has one triangle
120    #[test]
121    fn calculate_tangents_1_triangle() {
122        let mesh: BlenderMesh = BlenderMesh {
123            multi_indexed_vertex_attributes: MultiIndexedVertexAttributes {
124                positions: (
125                    vec![0, 1, 2],
126                    (
127                        concat_vecs!(v(0), vec![1.0, 0.0, 0.0], vec![1.0, 1.0, 0.0]),
128                        3,
129                    )
130                        .into(),
131                )
132                    .into(),
133                uvs: Some(
134                    (
135                        vec![0, 1, 2],
136                        (concat_vecs!(v2(0), vec![0.5, 0.0], v2(1)), 2).into(),
137                    )
138                        .into(),
139                ),
140                vertices_in_each_face: vec![3],
141                ..MultiIndexedVertexAttributes::default()
142            },
143            ..BlenderMesh::default()
144        };
145
146        mesh.calculate_face_tangents().unwrap();
147
148        assert_eq!(
149            &mesh.calculate_face_tangents().unwrap(),
150            // One face (a triangle) so only one face tangent vector
151            &vec![2., 0., 0.]
152        );
153    }
154
155    #[test]
156    fn calculate_tangents_2_triangle() {
157        let mesh: BlenderMesh = BlenderMesh {
158            multi_indexed_vertex_attributes: MultiIndexedVertexAttributes {
159                positions: (
160                    vec![0, 1, 2, 0, 2, 3],
161                    (
162                        concat_vecs!(
163                            v(0),
164                            vec![1.0, 0.0, 0.0],
165                            vec![1.0, 1.0, 0.0],
166                            vec![0., 1., 0.]
167                        ),
168                        3,
169                    )
170                        .into(),
171                )
172                    .into(),
173                uvs: Some(
174                    (
175                        vec![0, 1, 2, 0, 2, 3],
176                        (concat_vecs!(v2(0), vec![0.5, 0.0], v2(1), vec![0., 1.]), 2).into(),
177                    )
178                        .into(),
179                ),
180                vertices_in_each_face: vec![3, 3],
181                ..MultiIndexedVertexAttributes::default()
182            },
183            ..BlenderMesh::default()
184        };
185
186        mesh.calculate_face_tangents().unwrap();
187
188        assert_eq!(
189            &mesh.calculate_face_tangents().unwrap(),
190            // Two faces (two triangles) so two tangent vectors
191            &vec![2., 0., 0., 1., 0., 0.]
192        );
193    }
194
195    #[test]
196    fn calculate_tangents_1_quad() {
197        let mesh: BlenderMesh = BlenderMesh {
198            multi_indexed_vertex_attributes: MultiIndexedVertexAttributes {
199                positions: (
200                    vec![0, 1, 2, 3],
201                    (
202                        concat_vecs!(
203                            v(0),
204                            vec![1.0, 0.0, 0.0],
205                            vec![1.0, 1.0, 0.0],
206                            vec![0., 1., 0.]
207                        ),
208                        3,
209                    )
210                        .into(),
211                )
212                    .into(),
213                uvs: Some(
214                    (
215                        vec![0, 1, 2, 3],
216                        (concat_vecs!(v2(0), vec![0.5, 0.0], v2(1), vec![0., 1.]), 2).into(),
217                    )
218                        .into(),
219                ),
220                vertices_in_each_face: vec![4],
221                ..MultiIndexedVertexAttributes::default()
222            },
223            ..BlenderMesh::default()
224        };
225
226        mesh.calculate_face_tangents().unwrap();
227
228        assert_eq!(
229            &mesh.calculate_face_tangents().unwrap(),
230            // Two faces (two triangles) so two tangent vectors
231            &vec![2., 0., 0.]
232        );
233    }
234}