blender_mesh/combine_indices/
mod.rs

1pub use self::create_single_index_config::CreateSingleIndexConfig;
2use crate::face_tangents::face_tangent_at_idx;
3use crate::vertex_attributes::{BoneAttributes, SingleIndexedVertexAttributes, VertexAttribute};
4use crate::{BlenderMesh, BoneInfluence, Vertex};
5use std::collections::HashMap;
6use std::collections::HashSet;
7use std::ops::{Deref, DerefMut};
8
9mod create_single_index_config;
10mod weighted_normals;
11
12/// Used to set temporary data that should get overwritten.
13///
14/// So, if we ever see this number in our data it should make it easier to see that the
15/// data was improperly generated somehow.
16///
17/// Our unit tests should prevent this from ever happening - so this is just a safety precaution
18/// to more easily notice any errors.
19const EASILY_RECOGNIZABLE_NUMBER: f32 = 123456789.;
20
21impl BlenderMesh {
22    /// We store our exported Blender mesh with indices for positions, normals and uvs because
23    ///
24    ///  1) Easier because we we can unit test that here vs. a blender python script that's much
25    ///     trickier to test.
26    ///
27    ///  2) Smaller to store the fields indexed than to expand them.
28    ///
29    /// ---
30    ///
31    /// Most rendering pipelines only supports one index buffer, so here we convert our vertex data
32    /// from having three indices to having one.
33    ///
34    /// This typically requires some duplication of vertex data - we duplicate the minimum amount
35    /// of vertex data necessary.
36    ///
37    /// TODO: Need to continue refactoring
38    ///
39    /// TODO: Make this function set BlenderMesh.vertex_attributes = VertexData::SingleIndexVertexData
40    ///
41    /// TODO: Don't work on additionally functionality until we've broken up these tests
42    /// and implementation into smaller, specific pieces.
43    ///
44    /// TODO: There are unexpected (based on the method's name) mutations in here such as
45    /// triangulation. Lot's to refactor in this crate.
46    pub fn combine_vertex_indices(
47        &mut self,
48        config: &CreateSingleIndexConfig,
49    ) -> SingleIndexedVertexAttributes {
50        let mut face_tangents = None;
51
52        if let Some(bone_influences_per_vertex) = config.bone_influences_per_vertex {
53            self.multi_indexed_vertex_attributes
54                .set_bone_influences_per_vertex(bone_influences_per_vertex);
55        }
56
57        // Important to calculate face tangents before we modify / weight the normals
58        if config.calculate_face_tangents {
59            face_tangents = Some(self.calculate_face_tangents().unwrap());
60        }
61
62        let multi = &self.multi_indexed_vertex_attributes;
63
64        let mut largest_vert_id = *multi.positions.indices.iter().max().unwrap() as usize;
65
66        let mut encountered_vert_data = EncounteredIndexCombinations::default();
67
68        let mut encountered_vert_ids = HashSet::new();
69
70        let mut expanded_positions = vec![];
71        expanded_positions.resize((largest_vert_id + 1) * 3, EASILY_RECOGNIZABLE_NUMBER);
72
73        let mut expanded_normals = vec![];
74        expanded_normals.resize((largest_vert_id + 1) * 3, EASILY_RECOGNIZABLE_NUMBER);
75
76        let mut expanded_uvs = vec![];
77        expanded_uvs.resize((largest_vert_id + 1) * 2, EASILY_RECOGNIZABLE_NUMBER);
78
79        let mut expanded_pos_indices = vec![];
80
81        let mut new_group_indices = multi
82            .bone_influences
83            .as_ref()
84            .map(|b| b.bone_indices.clone());
85        let mut new_group_weights = multi
86            .bone_influences
87            .as_ref()
88            .map(|b| b.bone_weights.clone());
89
90        expanded_pos_indices.resize(multi.positions.indices.len(), 0);
91
92        let mut face_idx = 0;
93        let mut vertices_until_next_face = multi.vertices_in_each_face[0];
94
95        let mut expanded_tangents = vec![];
96        expanded_tangents.resize((largest_vert_id + 1) * 3, EASILY_RECOGNIZABLE_NUMBER);
97
98        // FIXME: Split this loop into a function
99        for (elem_array_index, start_vert_id) in multi.positions.indices.iter().enumerate() {
100            let start_vert_id = *start_vert_id;
101            let normal_index = match multi.normals.as_ref() {
102                None => None,
103                Some(normals) => Some(normals.indices[elem_array_index]),
104            };
105
106            let uv_index = match multi.uvs.as_ref() {
107                Some(uvs) => Some(uvs.indices[elem_array_index]),
108                None => None,
109            };
110
111            let vert_id_to_reuse =
112                encountered_vert_data.get(&(start_vert_id, normal_index, uv_index));
113
114            // If we've already seen this combination of vertex indices we'll re-use the index
115            if vert_id_to_reuse.is_some() {
116                expanded_pos_indices[elem_array_index] = *vert_id_to_reuse.unwrap();
117
118                if let Some(face_tangents) = &face_tangents {
119                    if face_tangents.len() > 0 {
120                        let (x, y, z) = face_tangent_at_idx(face_tangents, face_idx);
121                        // TODO: Should we weight these based on the surface area of the face /
122                        // the angle of the vertex and it's two edges on the face? Do some research
123                        // on what other people do.
124                        let vert_id_to_reuse = *vert_id_to_reuse.unwrap() as usize;
125                        expanded_tangents[vert_id_to_reuse * 3] += x;
126                        expanded_tangents[vert_id_to_reuse * 3 + 1] += y;
127                        expanded_tangents[vert_id_to_reuse * 3 + 2] += z;
128                    }
129                }
130            } else if !encountered_vert_ids.contains(&start_vert_id) {
131                // If this is our first time seeing this vertex index of vertex indices we'll insert
132                // the expanded data
133
134                encountered_vert_ids.insert(start_vert_id);
135
136                // TODO: Use a data structure that holds some of this stuff so we don't need
137                // to pass it around everywhere ..
138                self.handle_first_vertex_encounter(
139                    &face_tangents,
140                    &mut encountered_vert_data,
141                    &mut expanded_pos_indices,
142                    start_vert_id,
143                    elem_array_index,
144                    &mut expanded_positions,
145                    &mut expanded_normals,
146                    &mut expanded_uvs,
147                    &mut expanded_tangents,
148                    normal_index,
149                    uv_index,
150                    face_idx,
151                );
152            } else {
153                // If we've encountered an existing position index but the normal / uv indices for this
154                // vertex aren't the same as ones that we've previously encountered we'll need to
155                // create a new vertex index with this new combination of data.
156
157                largest_vert_id += 1;
158
159                expanded_pos_indices[elem_array_index] = largest_vert_id as u16;
160
161                self.push_generated_vertex_data(
162                    start_vert_id,
163                    normal_index,
164                    &face_tangents,
165                    uv_index,
166                    config.bone_influences_per_vertex,
167                    new_group_indices.as_mut(),
168                    new_group_weights.as_mut(),
169                    &mut expanded_positions,
170                    &mut expanded_normals,
171                    &mut expanded_uvs,
172                    &mut expanded_tangents,
173                    face_idx,
174                );
175
176                encountered_vert_data.insert(
177                    (start_vert_id as u16, normal_index, uv_index),
178                    largest_vert_id as u16,
179                );
180            }
181
182            if face_idx + 1 < multi.vertices_in_each_face.len() {
183                vertices_until_next_face -= 1;
184            }
185
186            if vertices_until_next_face == 0 {
187                face_idx += 1;
188                if face_idx < multi.vertices_in_each_face.len() {
189                    vertices_until_next_face = multi.vertices_in_each_face[face_idx];
190                }
191            }
192        }
193
194        let normals = match self.multi_indexed_vertex_attributes.normals.is_some() {
195            false => None,
196            true => Some(expanded_normals),
197        };
198        let uvs = match self.multi_indexed_vertex_attributes.uvs.is_some() {
199            false => None,
200            true => Some(expanded_uvs),
201        };
202
203        let bones = match (
204            &self.multi_indexed_vertex_attributes.bone_influences,
205            config.bone_influences_per_vertex,
206        ) {
207            (Some(_bone_attributes), Some(bone_influences_per_vertex)) => Some((
208                BoneAttributes {
209                    bone_influencers: VertexAttribute::new(
210                        new_group_indices.unwrap(),
211                        bone_influences_per_vertex,
212                    )
213                    .unwrap(),
214                    bone_weights: VertexAttribute::new(
215                        new_group_weights.unwrap(),
216                        bone_influences_per_vertex,
217                    )
218                    .unwrap(),
219                },
220                bone_influences_per_vertex,
221            )),
222            _ => None,
223        };
224
225        let tangents = face_tangents.map(|_| expanded_tangents);
226
227        let mut single_indexed_vertex_attributes = SingleIndexedVertexAttributes {
228            indices: expanded_pos_indices,
229            vertices: make_vertices(expanded_positions, normals, uvs, tangents, bones),
230        };
231
232        let indices = self.triangulate(&single_indexed_vertex_attributes.indices);
233        single_indexed_vertex_attributes.indices = indices;
234
235        single_indexed_vertex_attributes
236    }
237
238    // TODO: Way too many parameters - just working on splitting things up into smaller functions..
239    fn handle_first_vertex_encounter(
240        &self,
241        face_tangents: &Option<Vec<f32>>,
242        encountered_vert_data: &mut EncounteredIndexCombinations,
243        expanded_pos_indices: &mut Vec<u16>,
244        start_vert_id: u16,
245        elem_array_index: usize,
246        expanded_positions: &mut Vec<f32>,
247        expanded_normals: &mut Vec<f32>,
248        expanded_uvs: &mut Vec<f32>,
249        expanded_tangents: &mut Vec<f32>,
250        normal_index: Option<u16>,
251        uv_index: Option<u16>,
252        face_idx: usize,
253    ) {
254        let multi = &self.multi_indexed_vertex_attributes;
255
256        expanded_pos_indices[elem_array_index] = start_vert_id;
257
258        let start_vert_id = start_vert_id as usize;
259
260        // TODO: Six methods to get and set the normal, pos, and uv for a vertex_num
261        if let &[x, y, z] = multi.positions.attribute.data_at_idx(start_vert_id as u16) {
262            expanded_positions[start_vert_id * 3] = x;
263            expanded_positions[start_vert_id * 3 + 1] = y;
264            expanded_positions[start_vert_id * 3 + 2] = z;
265        }
266
267        if let Some(normal_index) = normal_index {
268            if let &[x, y, z] = multi
269                .normals
270                .as_ref()
271                .unwrap()
272                .attribute
273                .data_at_idx(normal_index)
274            {
275                expanded_normals[start_vert_id * 3] = x;
276                expanded_normals[start_vert_id * 3 + 1] = y;
277                expanded_normals[start_vert_id * 3 + 2] = z;
278            }
279        }
280
281        if let Some(uv_index) = uv_index {
282            if let &[u, v] = multi.uvs.as_ref().unwrap().attribute.data_at_idx(uv_index) {
283                expanded_uvs[start_vert_id * 2] = u;
284                expanded_uvs[start_vert_id * 2 + 1] = v;
285            }
286        }
287
288        if let Some(face_tangents) = face_tangents {
289            if face_tangents.len() > 0 {
290                let (x, y, z) = face_tangent_at_idx(&face_tangents, face_idx);
291                expanded_tangents[start_vert_id * 3] = x;
292                expanded_tangents[start_vert_id * 3 + 1] = y;
293                expanded_tangents[start_vert_id * 3 + 2] = z;
294            }
295        }
296
297        let start_vert_id = start_vert_id as u16;
298
299        encountered_vert_data.insert((start_vert_id, normal_index, uv_index), start_vert_id);
300    }
301
302    // TODO: Way too many parameters - just working on splitting things up into smaller functions..
303    fn push_generated_vertex_data(
304        &self,
305        pos_idx: u16,
306        normal_idx: Option<u16>,
307        face_tangents: &Option<Vec<f32>>,
308        uv_idx: Option<u16>,
309        bone_influences_per_vertex: Option<u8>,
310        new_group_indices: Option<&mut Vec<u8>>,
311        new_group_weights: Option<&mut Vec<f32>>,
312        expanded_positions: &mut Vec<f32>,
313        expanded_normals: &mut Vec<f32>,
314        expanded_uvs: &mut Vec<f32>,
315        expanded_tangents: &mut Vec<f32>,
316        face_idx: usize,
317    ) {
318        let multi = &self.multi_indexed_vertex_attributes;
319
320        if let &[x, y, z] = multi.positions.attribute.data_at_idx(pos_idx) {
321            expanded_positions.push(x);
322            expanded_positions.push(y);
323            expanded_positions.push(z);
324        }
325
326        if let Some(normal_idx) = normal_idx {
327            if let &[x, y, z] = multi
328                .normals
329                .as_ref()
330                .unwrap()
331                .attribute
332                .data_at_idx(normal_idx)
333            {
334                expanded_normals.push(x);
335                expanded_normals.push(y);
336                expanded_normals.push(z);
337            }
338        }
339
340        if let Some(uvs) = &multi.uvs {
341            let uv_index = uv_idx.unwrap();
342            if let &[u, v] = uvs.attribute.data_at_idx(uv_index) {
343                expanded_uvs.push(u);
344                expanded_uvs.push(v);
345            }
346        }
347
348        if let Some(face_tangents) = face_tangents {
349            if face_tangents.len() > 0 {
350                let (x, y, z) = face_tangent_at_idx(face_tangents, face_idx);
351                expanded_tangents.push(x);
352                expanded_tangents.push(y);
353                expanded_tangents.push(z);
354            }
355        }
356
357        // If the mesh has bone influences append bone data to the end of the bone vectors
358        // to account for this newly generated vertex.
359        if let Some(bone_influences_per_vertex) = bone_influences_per_vertex {
360            self.push_bone_data_for_generated_vertex(
361                pos_idx as usize,
362                bone_influences_per_vertex,
363                new_group_indices.unwrap(),
364                new_group_weights.unwrap(),
365            );
366        }
367    }
368
369    // TODO: Way too many parameters - just working on splitting things up into smaller functions..
370    fn push_bone_data_for_generated_vertex(
371        &self,
372        vert_idx: usize,
373        bone_influences_per_vertex: u8,
374        new_group_indices: &mut Vec<u8>,
375        new_group_weights: &mut Vec<f32>,
376    ) {
377        // Where in our vector of group indices / weights does this vertex start?
378        let group_data_start_idx = vert_idx * bone_influences_per_vertex as usize;
379
380        for i in 0..bone_influences_per_vertex {
381            let group_data_idx = group_data_start_idx + i as usize;
382            let weight = new_group_weights[group_data_idx];
383            new_group_weights.push(weight);
384
385            let index = new_group_indices[group_data_idx];
386            new_group_indices.push(index);
387        }
388    }
389}
390
391type PosIndex = u16;
392type NormalIndex = Option<u16>;
393type UvIndex = Option<u16>;
394#[derive(Debug, Default)]
395struct EncounteredIndexCombinations {
396    encountered: HashMap<(PosIndex, NormalIndex, UvIndex), PosIndex>,
397}
398
399impl Deref for EncounteredIndexCombinations {
400    type Target = HashMap<(PosIndex, NormalIndex, UvIndex), PosIndex>;
401
402    fn deref(&self) -> &Self::Target {
403        &self.encountered
404    }
405}
406
407impl DerefMut for EncounteredIndexCombinations {
408    fn deref_mut(&mut self) -> &mut Self::Target {
409        &mut self.encountered
410    }
411}
412
413// TODO: We're just throwing things around as we work to refactor this crate ...
414fn make_vertices(
415    vertex_positions: Vec<f32>,
416    vertex_normals: Option<Vec<f32>>,
417    vertex_uvs: Option<Vec<f32>>,
418    tangents: Option<Vec<f32>>,
419    bones: Option<(BoneAttributes, u8)>,
420) -> Vec<Vertex> {
421    let mut vertices = vec![];
422    for idx in 0..vertex_positions.len() / 3 {
423        let position = [
424            vertex_positions[idx * 3],
425            vertex_positions[idx * 3 + 1],
426            vertex_positions[idx * 3 + 2],
427        ];
428        let normal = vertex_normals
429            .as_ref()
430            .map(|n| [n[idx * 3], n[idx * 3 + 1], n[idx * 3 + 2]]);
431        let uv = vertex_uvs
432            .as_ref()
433            .map(|uvs| [uvs[idx * 2], uvs[idx * 2 + 1]]);
434        let face_tangent = tangents.as_ref().map(|face_tangents| {
435            [
436                face_tangents[idx * 3],
437                face_tangents[idx * 3 + 1],
438                face_tangents[idx * 3 + 2],
439            ]
440        });
441        let bones = bones.as_ref().map(|(b, influences_per_vertex)| {
442            let count = *influences_per_vertex;
443
444            let mut bones = [BoneInfluence {
445                bone_idx: 0,
446                weight: 0.0,
447            }; 4];
448            for bone_idx in 0..count as usize {
449                bones[bone_idx] = BoneInfluence {
450                    bone_idx: b.bone_influencers[idx * count as usize + bone_idx],
451                    weight: b.bone_weights[idx * count as usize + bone_idx],
452                };
453            }
454
455            bones
456        });
457        vertices.push(Vertex {
458            position,
459            normal,
460            face_tangent,
461            uv,
462            bones,
463        });
464    }
465
466    vertices
467}
468
469/// TODO: These tests are getting hard to manage.
470/// We need smaller tests that test individual pieces of the combining.
471/// Then we can keep it to only a handful of tests that test entire meshes.
472/// TODO: Don't work on additionally functionality until we've broken up these tests
473/// and implementation into smaller, specific pieces.
474#[cfg(test)]
475pub mod tests {
476    use super::*;
477    use crate::bone::BoneInfluencesPerVertex;
478    use crate::concat_vecs;
479    use crate::test_utils::*;
480    use crate::vertex_attributes::{
481        BoneAttributes, IndexedAttribute, MultiIndexedVertexAttributes, VertexBoneInfluences,
482    };
483
484    struct CombineIndicesTest {
485        mesh_to_combine: BlenderMesh,
486        expected_combined_mesh: SingleIndexedVertexAttributes,
487        create_single_idx_config: Option<CreateSingleIndexConfig>,
488    }
489
490    impl CombineIndicesTest {
491        fn test(mut self) {
492            let combined = self.mesh_to_combine.combine_vertex_indices(
493                self.create_single_idx_config
494                    .as_ref()
495                    .unwrap_or(&CreateSingleIndexConfig::default()),
496            );
497
498            assert_eq!(combined, self.expected_combined_mesh);
499        }
500    }
501
502    #[test]
503    fn combine_pos_norm_indices() {
504        let mesh_to_combine = make_mesh_to_combine_without_uvs();
505        let expected_combined_mesh = make_expected_combined_mesh();
506
507        let create_single_idx_config = Some(CreateSingleIndexConfig {
508            bone_influences_per_vertex: Some(3),
509            calculate_face_tangents: false,
510            ..CreateSingleIndexConfig::default()
511        });
512
513        CombineIndicesTest {
514            mesh_to_combine,
515            expected_combined_mesh,
516            create_single_idx_config,
517        }
518        .test();
519    }
520
521    // Verify that we do not panic if we're combining indices where some of the indices have
522    // larger indices coming before smaller indices.
523    //
524    // This ensures that we properly resize our final data vectors before we start pushing data
525    // to them, vs. trying to set data into an index that is larger than the length of the
526    // vector at the time.
527    #[test]
528    fn combine_mesh_with_non_sequential_indices() {
529        let mesh_to_combine = BlenderMesh {
530            multi_indexed_vertex_attributes: TodoDeleteMeMultiConverter {
531                vertex_positions: concat_vecs!(v(5), v(6), v(7)),
532                vertex_normals: concat_vecs!(v(10), v(11), v(12)),
533                vertex_uvs: Some(concat_vecs!(v2(15), v2(16), v2(17))),
534                bone_influences_per_vertex: None,
535                vertex_group_indices: None,
536                num_vertices_in_each_face: vec![3],
537                vertex_position_indices: vec![2, 1, 0],
538                vertex_normal_indices: vec![2, 1, 0],
539                vertex_uv_indices: Some(vec![2, 1, 0]),
540                vertex_group_weights: None,
541            }
542            .into(),
543            ..BlenderMesh::default()
544        };
545
546        let expected_combined_mesh = TodoDeleteMeSingleConverter {
547            vertex_position_indices: vec![2, 1, 0],
548            vertex_positions: concat_vecs!(v(5), v(6), v(7)),
549            vertex_normals: concat_vecs!(v(10), v(11), v(12)),
550            vertex_uvs: Some(concat_vecs!(v2(15), v2(16), v2(17))),
551            num_vertices_in_each_face: vec![3],
552            tangents: None,
553            bone_influences_per_vertex: None,
554            vertex_group_indices: None,
555            vertex_group_weights: None,
556        }
557        .into();
558
559        CombineIndicesTest {
560            mesh_to_combine,
561            expected_combined_mesh,
562            create_single_idx_config: None,
563        }
564        .test();
565    }
566
567    // We create a mesh that might have been triangulated before it was exported from Blender.
568    // Before this test we weren't combining our normals properly after using the `triangulate`
569    // modifier in Blender.
570    #[test]
571    fn combine_already_triangulated_mesh() {
572        let mesh_to_combine = BlenderMesh {
573            multi_indexed_vertex_attributes: TodoDeleteMeMultiConverter {
574                vertex_positions: concat_vecs!(v(5), v(6), v(7), v(8)),
575                vertex_normals: concat_vecs!(
576                    v(10),
577                    v(11),
578                    v(12),
579                    v(13),
580                    v(14),
581                    v(15),
582                    v(16),
583                    v(17)
584                ),
585                num_vertices_in_each_face: vec![3, 3, 3],
586                vertex_position_indices: concat_vecs!(vec![0, 1, 2], vec![0, 2, 3], vec![0, 2, 3]),
587                vertex_normal_indices: concat_vecs!(vec![0, 1, 2], vec![0, 2, 3], vec![4, 5, 6]),
588                ..TodoDeleteMeMultiConverter::default()
589            }
590            .into(),
591            ..BlenderMesh::default()
592        };
593
594        let expected_combined_mesh = TodoDeleteMeSingleConverter {
595            vertex_positions: concat_vecs!(v3_x3(5, 6, 7), v(8), v3_x3(5, 7, 8)),
596            vertex_position_indices: concat_vecs![vec![0, 1, 2], vec![0, 2, 3], vec![4, 5, 6]],
597            num_vertices_in_each_face: vec![3, 3, 3],
598            vertex_normals: concat_vecs!(v3_x3(10, 11, 12), v(13), v3_x3(14, 15, 16)),
599            ..TodoDeleteMeSingleConverter::default()
600        }
601        .into();
602
603        CombineIndicesTest {
604            mesh_to_combine,
605            expected_combined_mesh,
606            create_single_idx_config: None,
607        }
608        .test();
609    }
610
611    // We create a mesh where our first three triangles have no repeating vertices
612    // (across norms, uvs and positions) then our fourth triangle has all repeating vertices
613    #[test]
614    fn combine_pos_norm_uv_indices() {
615        let mesh_to_combine = mesh_to_combine_pos_norm_uv_indices();
616        let expected_combined_mesh = expected_mesh_to_combine_pos_norm_uv_indices();
617
618        CombineIndicesTest {
619            mesh_to_combine,
620            expected_combined_mesh,
621            create_single_idx_config: None,
622        }
623        .test();
624    }
625
626    fn mesh_to_combine_pos_norm_uv_indices() -> BlenderMesh {
627        BlenderMesh {
628            multi_indexed_vertex_attributes: TodoDeleteMeMultiConverter {
629                vertex_positions: concat_vecs!(v(0), v(1), v(2), v(3)),
630                vertex_normals: concat_vecs!(v(4), v(5), v(6)),
631                num_vertices_in_each_face: vec![4, 4, 4, 4],
632                vertex_position_indices: concat_vecs!(
633                    vec![0, 1, 2, 3],
634                    vec![0, 1, 2, 3],
635                    vec![0, 1, 2, 3],
636                    vec![0, 1, 2, 3]
637                ),
638                vertex_normal_indices: concat_vecs!(
639                    vec![0, 1, 0, 1],
640                    vec![2, 2, 2, 2],
641                    vec![2, 2, 2, 2],
642                    vec![2, 2, 2, 2]
643                ),
644                vertex_uvs: Some(concat_vecs!(v2(7), v2(8), v2(9), v2(10))),
645                vertex_uv_indices: Some(concat_vecs!(
646                    vec![0, 1, 0, 1],
647                    vec![2, 2, 2, 2],
648                    vec![3, 3, 3, 3],
649                    vec![3, 3, 3, 3]
650                )),
651                ..TodoDeleteMeMultiConverter::default()
652            }
653            .into(),
654
655            // We already tested vertex group indices / weights about so not bothering setting up
656            // more test data
657            ..BlenderMesh::default()
658        }
659    }
660
661    fn expected_mesh_to_combine_pos_norm_uv_indices() -> SingleIndexedVertexAttributes {
662        TodoDeleteMeSingleConverter {
663            vertex_positions: concat_vecs!(v3_x4(0, 1, 2, 3), v3_x4(0, 1, 2, 3), v3_x4(0, 1, 2, 3)),
664            vertex_position_indices: concat_vecs![
665                // First Triangle
666                vec![0, 1, 2, 0, 2, 3,],
667                // Second Triangle
668                vec![4, 5, 6, 4, 6, 7],
669                // Third Triangle
670                vec![8, 9, 10, 8, 10, 11],
671                // Fourth Triangle
672                vec![8, 9, 10, 8, 10, 11]
673            ],
674            num_vertices_in_each_face: vec![4, 4, 4, 4],
675            vertex_normals: concat_vecs!(v3_x4(4, 5, 4, 5), v3_x4(6, 6, 6, 6), v3_x4(6, 6, 6, 6)),
676            vertex_uvs: Some(concat_vecs!(
677                v2_x4(7, 8, 7, 8),
678                v2_x4(9, 9, 9, 9),
679                v2_x4(10, 10, 10, 10)
680            )),
681            ..TodoDeleteMeSingleConverter::default()
682        }
683        .into()
684    }
685
686    /// Verify that when we re-use a vertex we add in the tangent of the second vertex that we're
687    /// skipping to the first one that we're re-using.
688    ///
689    /// NOTE: Numbers in these tests were not verified by hand.
690    /// Instead, we took this common tangent calculation formula wrote tests, and verified
691    /// that the rendered models looked visually correct (meaning that our test values are also correct).
692    #[test]
693    fn calculate_per_vertex_tangents_encountered_duplicate_data() {
694        let mesh_to_combine = BlenderMesh {
695            multi_indexed_vertex_attributes: TodoDeleteMeMultiConverter {
696                vertex_positions: concat_vecs!(
697                    v(0),
698                    vec![1.0, 0.0, 0.0],
699                    vec![1.0, 1.0, 0.0],
700                    vec![0., 1., 0.]
701                ),
702                vertex_normals: concat_vecs!(v(4), v(5), v(6), v(7)),
703                num_vertices_in_each_face: vec![4, 4, 4, 4],
704                vertex_position_indices: concat_vecs!(
705                    vec![0, 1, 2, 3],
706                    vec![0, 1, 2, 3],
707                    vec![0, 1, 2, 3],
708                    vec![0, 1, 2, 3]
709                ),
710                vertex_normal_indices: concat_vecs!(
711                    vec![0, 1, 2, 3],
712                    vec![0, 1, 2, 3],
713                    vec![0, 1, 2, 3],
714                    vec![0, 1, 2, 3]
715                ),
716                vertex_uvs: Some(concat_vecs!(v2(0), vec![0.5, 0.0], v2(1), vec![0., 1.])),
717                vertex_uv_indices: Some(concat_vecs!(
718                    vec![0, 1, 2, 3], // .
719                    vec![0, 1, 2, 3], // .
720                    vec![0, 1, 2, 3], // .
721                    vec![0, 1, 2, 3]  // .
722                )),
723                ..TodoDeleteMeMultiConverter::default()
724            }
725            .into(),
726            ..BlenderMesh::default()
727        };
728
729        assert_eq!(
730            mesh_to_combine.calculate_face_tangents().unwrap(),
731            vec![2.0, 0.0, 0.0, 2.0, 0.0, 0.0, 2.0, 0.0, 0.0, 2.0, 0.0, 0.0,]
732        );
733
734        let expected_combined_mesh = TodoDeleteMeSingleConverter {
735            vertex_positions: vec![0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 1.0, 0.0],
736            vertex_position_indices: vec![
737                0, 1, 2, 0, 2, 3, 0, 1, 2, 0, 2, 3, 0, 1, 2, 0, 2, 3, 0, 1, 2, 0, 2, 3,
738            ],
739            num_vertices_in_each_face: vec![4, 4, 4, 4],
740            vertex_normals: vec![4.0, 4.0, 4.0, 5.0, 5.0, 5.0, 6.0, 6.0, 6.0, 7.0, 7.0, 7.0],
741            vertex_uvs: Some(vec![0.0, 0.0, 0.5, 0.0, 1.0, 1.0, 0.0, 1.0]),
742            // 4 duplicate vertices, each with [2., 0., 0.] as the tangent
743            // When combined we get [8., 0., 0.]
744            tangents: Some(vec![
745                8.0, 0.0, 0.0, 8.0, 0.0, 0.0, 8.0, 0.0, 0.0, 8.0, 0.0, 0.0,
746            ]),
747            ..TodoDeleteMeSingleConverter::default()
748        }
749        .into();
750
751        let create_single_idx_config = Some(CreateSingleIndexConfig {
752            bone_influences_per_vertex: None,
753            calculate_face_tangents: true,
754            ..CreateSingleIndexConfig::default()
755        });
756
757        CombineIndicesTest {
758            mesh_to_combine,
759            expected_combined_mesh,
760            create_single_idx_config,
761        }
762        .test();
763    }
764
765    fn make_mesh_to_combine_without_uvs() -> BlenderMesh {
766        let start_positions = concat_vecs!(v(0), v(1), v(2), v(3));
767        let start_normals = concat_vecs!(v(4), v(5), v(6));
768
769        BlenderMesh {
770            multi_indexed_vertex_attributes: TodoDeleteMeMultiConverter {
771                vertex_positions: start_positions,
772                vertex_position_indices: vec![0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3],
773                num_vertices_in_each_face: vec![4, 4, 4],
774                vertex_normals: start_normals,
775                // Our last 4 vertices already exist so our expected mesh will generate
776                // position indices 4, 5, 6 and 7 and use those for the second to last 4 and
777                // then last 4 indices
778                vertex_normal_indices: vec![0, 1, 0, 1, 2, 2, 2, 2, 2, 2, 2, 2],
779                vertex_uv_indices: None,
780                vertex_uvs: None,
781                bone_influences_per_vertex: Some(vec![3, 2, 5, 1].into()),
782                vertex_group_indices: Some(vec![0, 1, 2, 0, 3, 4, 5, 6, 7, 8, 11]),
783                vertex_group_weights: Some(vec![
784                    0.05, 0.8, 0.15, 0.5, 0.5, 0.1, 0.2, 0.2, 0.2, 0.3, 0.999,
785                ]),
786            }
787            .into(),
788            ..BlenderMesh::default()
789        }
790    }
791
792    fn make_expected_combined_mesh() -> SingleIndexedVertexAttributes {
793        let end_positions = concat_vecs!(v(0), v(1), v(2), v(3), v(0), v(1), v(2), v(3));
794        let end_normals = concat_vecs!(v(4), v(5), v(4), v(5), v(6), v(6), v(6), v(6));
795
796        TodoDeleteMeSingleConverter {
797            vertex_positions: end_positions,
798            vertex_position_indices: vec![0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7, 4, 5, 6, 4, 6, 7],
799            num_vertices_in_each_face: vec![4, 4, 4],
800            vertex_normals: end_normals,
801            bone_influences_per_vertex: Some(BoneInfluencesPerVertex::Uniform(3)),
802            // Config.bone_influences_per_vertex = 3
803            vertex_group_indices: Some(vec![
804                1, 2, 0, 0, 3, 0, 8, 5, 6, 11, 0, 0, 1, 2, 0, 0, 3, 0, 8, 5, 6, 11, 0, 0,
805            ]),
806            // Config.bone_influences_per_vertex = 3
807            vertex_group_weights: Some(vec![
808                0.8, 0.15, 0.05, 0.5, 0.5, 0.0, 0.3, 0.2, 0.2, 0.999, 0.0, 0.0, 0.8, 0.15, 0.05,
809                0.5, 0.5, 0.0, 0.3, 0.2, 0.2, 0.999, 0.0, 0.0,
810            ]),
811            tangents: None,
812            vertex_uvs: None,
813        }
814        .into()
815    }
816
817    // We changed the format of the BlenderMesh in one refactoring PR - so this holds some test data
818    // with the old names and format so that we can quickly convert it into the new format.
819    // TODO: Remove this and just use the new format directly
820    #[derive(Default)]
821    pub struct TodoDeleteMeMultiConverter {
822        pub vertex_positions: Vec<f32>,
823        pub vertex_position_indices: Vec<u16>,
824        pub num_vertices_in_each_face: Vec<u8>,
825        pub vertex_normals: Vec<f32>,
826        pub vertex_normal_indices: Vec<u16>,
827        pub vertex_uv_indices: Option<Vec<u16>>,
828        pub vertex_uvs: Option<Vec<f32>>,
829        pub(crate) bone_influences_per_vertex: Option<BoneInfluencesPerVertex>,
830        // Config.bone_influences_per_vertex = 3
831        pub vertex_group_indices: Option<Vec<u8>>,
832        // Config.bone_influences_per_vertex = 3
833        pub vertex_group_weights: Option<Vec<f32>>,
834    }
835
836    impl Into<MultiIndexedVertexAttributes> for TodoDeleteMeMultiConverter {
837        fn into(self) -> MultiIndexedVertexAttributes {
838            let normals = Some(IndexedAttribute {
839                indices: self.vertex_normal_indices,
840                attribute: (self.vertex_normals, 3).into(),
841            });
842
843            let mut uvs = None;
844            let mut parent_armature_bone_influences = None;
845
846            if self.vertex_uv_indices.is_some() {
847                uvs = Some(IndexedAttribute {
848                    indices: self.vertex_uv_indices.unwrap(),
849                    attribute: (self.vertex_uvs.unwrap(), 2).into(),
850                })
851            }
852
853            if let Some(bone_influences_per_vertex) = self.bone_influences_per_vertex {
854                parent_armature_bone_influences = Some(VertexBoneInfluences {
855                    bones_per_vertex: bone_influences_per_vertex,
856                    bone_indices: self.vertex_group_indices.unwrap(),
857                    bone_weights: self.vertex_group_weights.unwrap(),
858                })
859            }
860
861            MultiIndexedVertexAttributes {
862                vertices_in_each_face: self.num_vertices_in_each_face,
863                positions: IndexedAttribute {
864                    indices: self.vertex_position_indices,
865                    attribute: (self.vertex_positions, 3).into(),
866                },
867                normals,
868                uvs,
869                bone_influences: parent_armature_bone_influences,
870            }
871        }
872    }
873
874    /// Messy code as part of an effort to slowly refactor these crates...
875    #[derive(Default)]
876    pub struct TodoDeleteMeSingleConverter {
877        pub vertex_positions: Vec<f32>,
878        pub vertex_position_indices: Vec<u16>,
879        pub num_vertices_in_each_face: Vec<u8>,
880        pub vertex_normals: Vec<f32>,
881        pub vertex_uvs: Option<Vec<f32>>,
882        pub tangents: Option<Vec<f32>>,
883        pub(crate) bone_influences_per_vertex: Option<BoneInfluencesPerVertex>,
884        pub vertex_group_indices: Option<Vec<u8>>,
885        pub vertex_group_weights: Option<Vec<f32>>,
886    }
887
888    impl Into<SingleIndexedVertexAttributes> for TodoDeleteMeSingleConverter {
889        fn into(self) -> SingleIndexedVertexAttributes {
890            let bones = match self.bone_influences_per_vertex.as_ref() {
891                None => None,
892                Some(b) => {
893                    let b = match b {
894                        BoneInfluencesPerVertex::NonUniform(_) => unreachable!(),
895                        BoneInfluencesPerVertex::Uniform(b) => *b as _,
896                    };
897
898                    Some((
899                        BoneAttributes {
900                            bone_influencers: (self.vertex_group_indices.unwrap(), b).into(),
901                            bone_weights: (self.vertex_group_weights.unwrap(), b).into(),
902                        },
903                        b,
904                    ))
905                }
906            };
907
908            SingleIndexedVertexAttributes {
909                indices: self.vertex_position_indices,
910                vertices: make_vertices(
911                    self.vertex_positions,
912                    Some(self.vertex_normals),
913                    self.vertex_uvs,
914                    self.tangents,
915                    bones,
916                ),
917            }
918        }
919    }
920}