pub use self::create_single_index_config::CreateSingleIndexConfig;
use crate::vertex_data::{AttributeSize, VertexAttribute};
use crate::BlenderMesh;
use std::collections::HashMap;
use std::collections::HashSet;
use std::ops::{Deref, DerefMut};
mod create_single_index_config;
const EASILY_RECOGNIZABLE_NUMBER: f32 = 123456789.;
impl BlenderMesh {
pub fn combine_vertex_indices(&mut self, config: &CreateSingleIndexConfig) {
if let Some(bone_influences_per_vertex) = config.bone_influences_per_vertex {
self.set_bone_influences_per_vertex(bone_influences_per_vertex);
}
if config.calculate_vertex_tangents {
self.calculate_face_tangents().unwrap();
}
let has_uvs = self.vertex_uvs.is_some();
let mut largest_vert_id = *self.vertex_position_indices.iter().max().unwrap() as usize;
let mut encountered_vert_data = EncounteredIndexCombinations::default();
let mut encountered_vert_ids = HashSet::new();
let mut expanded_positions = vec![];
expanded_positions.resize((largest_vert_id + 1) * 3, EASILY_RECOGNIZABLE_NUMBER);
let mut expanded_positions = VertexAttribute::new(expanded_positions, AttributeSize::Three);
let mut expanded_normals = vec![];
expanded_normals.resize((largest_vert_id + 1) * 3, EASILY_RECOGNIZABLE_NUMBER);
let mut expanded_normals = VertexAttribute::new(expanded_normals, AttributeSize::Three);
let mut expanded_uvs = vec![];
expanded_uvs.resize((largest_vert_id + 1) * 2, EASILY_RECOGNIZABLE_NUMBER);
let mut expanded_uvs = VertexAttribute::new(expanded_uvs, AttributeSize::Two);
let mut expanded_pos_indices = vec![];
let mut new_group_indices = self.vertex_group_indices.clone();
let mut new_group_weights = self.vertex_group_weights.clone();
expanded_pos_indices.resize(self.vertex_position_indices.len(), 0);
let mut face_idx = 0;
let mut vertices_until_next_face = self.num_vertices_in_each_face[0];
let mut expanded_tangents = vec![];
expanded_tangents.resize((largest_vert_id + 1) * 3, EASILY_RECOGNIZABLE_NUMBER);
let mut expanded_tangents = VertexAttribute::new(expanded_tangents, AttributeSize::Three);
let total_indices: usize = self
.num_vertices_in_each_face
.iter()
.map(|x| *x as usize)
.sum();
for (elem_array_index, start_vert_id) in self.vertex_position_indices.iter().enumerate() {
let start_vert_id = *start_vert_id;
let normal_index = self.vertex_normal_indices.as_ref().unwrap()[elem_array_index];
let uv_index = match self.vertex_uv_indices.as_ref() {
Some(uvs) => Some(uvs[elem_array_index]),
None => None,
};
let vert_id_to_reuse =
encountered_vert_data.get(&(start_vert_id, normal_index, uv_index));
if vert_id_to_reuse.is_some() {
expanded_pos_indices[elem_array_index] = *vert_id_to_reuse.unwrap();
if let Some(face_tangents) = &self.face_tangents {
if face_tangents.len() > 0 {
let (x, y, z) = self.face_tangent_at_idx(face_idx);
expanded_tangents.increment_three_components(
*vert_id_to_reuse.unwrap() as usize,
x,
y,
z,
);
}
}
} else if !encountered_vert_ids.contains(&start_vert_id) {
encountered_vert_ids.insert(start_vert_id);
self.handle_first_vertex_encounter(
&mut encountered_vert_data,
&mut expanded_pos_indices,
start_vert_id,
elem_array_index,
&mut expanded_positions,
&mut expanded_normals,
&mut expanded_uvs,
&mut expanded_tangents,
normal_index,
uv_index,
face_idx,
);
} else {
largest_vert_id += 1;
expanded_pos_indices[elem_array_index] = largest_vert_id as u16;
self.push_generated_vertex_data(
start_vert_id,
normal_index,
uv_index,
config.bone_influences_per_vertex,
new_group_indices.as_mut(),
new_group_weights.as_mut(),
&mut expanded_positions,
&mut expanded_normals,
&mut expanded_uvs,
&mut expanded_tangents,
face_idx,
);
encountered_vert_data.insert(
(start_vert_id as u16, normal_index, uv_index),
largest_vert_id as u16,
);
}
if face_idx + 1 < self.num_vertices_in_each_face.len() {
vertices_until_next_face -= 1;
}
if vertices_until_next_face == 0 {
face_idx += 1;
if face_idx < self.num_vertices_in_each_face.len() {
vertices_until_next_face = self.num_vertices_in_each_face[face_idx];
}
}
}
self.vertex_position_indices = expanded_pos_indices;
self.vertex_normals = expanded_normals.data().clone();
self.vertex_positions = expanded_positions.data().clone();
self.vertex_group_indices = new_group_indices;
self.vertex_group_weights = new_group_weights;
if config.calculate_vertex_tangents {
self.per_vertex_tangents = Some(expanded_tangents);
}
if has_uvs {
self.vertex_uvs = Some(expanded_uvs.data().clone());
}
self.vertex_normal_indices = None;
self.vertex_uv_indices = None;
}
fn handle_first_vertex_encounter(
&self,
encountered_vert_data: &mut EncounteredIndexCombinations,
expanded_pos_indices: &mut Vec<u16>,
start_vert_id: u16,
elem_array_index: usize,
expanded_positions: &mut VertexAttribute,
expanded_normals: &mut VertexAttribute,
expanded_uvs: &mut VertexAttribute,
expanded_tangents: &mut VertexAttribute,
normal_index: u16,
uv_index: Option<u16>,
face_idx: usize,
) {
let has_uvs = self.vertex_uvs.is_some();
expanded_pos_indices[elem_array_index] = start_vert_id;
let start_vert_id = start_vert_id as usize;
let (x, y, z) = self.vertex_pos_at_idx(start_vert_id as u16);
expanded_positions.set_three_components(start_vert_id, x, y, z);
let (x, y, z) = self.vertex_normal_at_idx(normal_index);
expanded_normals.set_three_components(start_vert_id, x, y, z);
if has_uvs {
let uv_index = uv_index.unwrap();
let (u, v) = self.vertex_uv_at_idx(uv_index);
expanded_uvs.set_two_components(start_vert_id, u, v);
}
if let Some(face_tangents) = &self.face_tangents {
if face_tangents.len() > 0 {
let (x, y, z) = self.face_tangent_at_idx(face_idx);
expanded_tangents.set_three_components(start_vert_id, x, y, z);
}
}
let start_vert_id = start_vert_id as u16;
encountered_vert_data.insert((start_vert_id, normal_index, uv_index), start_vert_id);
}
fn push_generated_vertex_data(
&self,
pos_idx: u16,
normal_idx: u16,
uv_idx: Option<u16>,
bone_influences_per_vertex: Option<u8>,
new_group_indices: Option<&mut Vec<u8>>,
new_group_weights: Option<&mut Vec<f32>>,
expanded_positions: &mut VertexAttribute,
expanded_normals: &mut VertexAttribute,
expanded_uvs: &mut VertexAttribute,
expanded_tangents: &mut VertexAttribute,
face_idx: usize,
) {
let has_uvs = self.vertex_uvs.is_some();
let (x, y, z) = self.vertex_pos_at_idx(pos_idx);
expanded_positions.push(x);
expanded_positions.push(y);
expanded_positions.push(z);
let (x, y, z) = self.vertex_normal_at_idx(normal_idx);
expanded_normals.push(x);
expanded_normals.push(y);
expanded_normals.push(z);
if has_uvs {
let uv_index = uv_idx.unwrap();
let (u, v) = self.vertex_uv_at_idx(uv_index);
expanded_uvs.push(u);
expanded_uvs.push(v);
}
if let Some(face_tangents) = &self.face_tangents {
if face_tangents.len() > 0 {
let (x, y, z) = self.face_tangent_at_idx(face_idx);
expanded_tangents.push(x);
expanded_tangents.push(y);
expanded_tangents.push(z);
}
}
if let Some(bone_influences_per_vertex) = bone_influences_per_vertex {
self.push_bone_data_for_generated_vertex(
pos_idx as usize,
bone_influences_per_vertex,
new_group_indices.unwrap(),
new_group_weights.unwrap(),
);
}
}
fn push_bone_data_for_generated_vertex(
&self,
vert_idx: usize,
bone_influences_per_vertex: u8,
new_group_indices: &mut Vec<u8>,
new_group_weights: &mut Vec<f32>,
) {
let group_data_start_idx = vert_idx * bone_influences_per_vertex as usize;
for i in 0..bone_influences_per_vertex {
let group_data_idx = group_data_start_idx + i as usize;
let weight = new_group_weights[group_data_idx];
new_group_weights.push(weight);
let index = new_group_indices[group_data_idx];
new_group_indices.push(index);
}
}
}
type PosIndex = u16;
type NormalIndex = u16;
type UvIndex = Option<u16>;
#[derive(Debug, Default)]
struct EncounteredIndexCombinations {
encountered: HashMap<(PosIndex, NormalIndex, UvIndex), PosIndex>,
}
impl Deref for EncounteredIndexCombinations {
type Target = HashMap<(PosIndex, NormalIndex, UvIndex), PosIndex>;
fn deref(&self) -> &Self::Target {
&self.encountered
}
}
impl DerefMut for EncounteredIndexCombinations {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.encountered
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::bone::BoneInfluencesPerVertex;
use crate::concat_vecs;
use crate::test_utils::*;
struct CombineIndicesTest {
mesh_to_combine: BlenderMesh,
expected_combined_mesh: BlenderMesh,
create_single_idx_config: Option<CreateSingleIndexConfig>,
}
impl CombineIndicesTest {
fn test(&mut self) {
self.mesh_to_combine.combine_vertex_indices(
self.create_single_idx_config
.as_ref()
.unwrap_or(&CreateSingleIndexConfig::default()),
);
let combined_mesh = &self.mesh_to_combine;
assert_eq!(combined_mesh, &self.expected_combined_mesh);
}
}
#[test]
fn combine_pos_norm_indices() {
let mesh_to_combine = make_mesh_to_combine_without_uvs();
let expected_combined_mesh = make_expected_combined_mesh();
let create_single_idx_config = Some(CreateSingleIndexConfig {
bone_influences_per_vertex: Some(3),
calculate_vertex_tangents: false,
});
CombineIndicesTest {
mesh_to_combine,
expected_combined_mesh,
create_single_idx_config,
}
.test();
}
#[test]
fn combine_mesh_with_non_sequential_indices() {
let mesh_to_combine = BlenderMesh {
vertex_positions: concat_vecs!(v(5), v(6), v(7)),
vertex_normals: concat_vecs!(v(10), v(11), v(12)),
vertex_uvs: Some(concat_vecs!(v2(15), v2(16), v2(17))),
num_vertices_in_each_face: vec![3],
vertex_position_indices: vec![2, 1, 0],
vertex_normal_indices: Some(vec![2, 1, 0]),
vertex_uv_indices: Some(vec![2, 1, 0]),
..BlenderMesh::default()
};
let expected_combined_mesh = BlenderMesh {
vertex_position_indices: vec![2, 1, 0],
vertex_positions: concat_vecs!(v(5), v(6), v(7)),
vertex_normals: concat_vecs!(v(10), v(11), v(12)),
vertex_uvs: Some(concat_vecs!(v2(15), v2(16), v2(17))),
num_vertices_in_each_face: vec![3],
..BlenderMesh::default()
};
CombineIndicesTest {
mesh_to_combine,
expected_combined_mesh,
create_single_idx_config: None,
}
.test();
}
#[test]
fn combine_already_triangulated_mesh() {
let mesh_to_combine = BlenderMesh {
vertex_positions: concat_vecs!(v(5), v(6), v(7), v(8)),
vertex_normals: concat_vecs!(v(10), v(11), v(12), v(13), v(14), v(15), v(16), v(17)),
num_vertices_in_each_face: vec![3, 3, 3],
vertex_position_indices: concat_vecs!(vec![0, 1, 2], vec![0, 2, 3], vec![0, 2, 3]),
vertex_normal_indices: Some(concat_vecs!(vec![0, 1, 2], vec![0, 2, 3], vec![4, 5, 6])),
..BlenderMesh::default()
};
let expected_combined_mesh = BlenderMesh {
vertex_positions: concat_vecs!(v3_x3(5, 6, 7), v(8), v3_x3(5, 7, 8)),
vertex_position_indices: concat_vecs![vec![0, 1, 2], vec![0, 2, 3], vec![4, 5, 6]],
num_vertices_in_each_face: vec![3, 3, 3],
vertex_normals: concat_vecs!(v3_x3(10, 11, 12), v(13), v3_x3(14, 15, 16)),
..BlenderMesh::default()
};
CombineIndicesTest {
mesh_to_combine,
expected_combined_mesh,
create_single_idx_config: None,
}
.test();
}
#[test]
fn combine_pos_norm_uv_indices() {
let mesh_to_combine = mesh_to_combine_pos_norm_uv_indices();
let expected_combined_mesh = expected_mesh_to_combine_pos_norm_uv_indices();
CombineIndicesTest {
mesh_to_combine,
expected_combined_mesh,
create_single_idx_config: None,
}
.test();
}
fn mesh_to_combine_pos_norm_uv_indices() -> BlenderMesh {
BlenderMesh {
vertex_positions: concat_vecs!(v(0), v(1), v(2), v(3)),
vertex_normals: concat_vecs!(v(4), v(5), v(6)),
num_vertices_in_each_face: vec![4, 4, 4, 4],
vertex_position_indices: concat_vecs!(
vec![0, 1, 2, 3],
vec![0, 1, 2, 3],
vec![0, 1, 2, 3],
vec![0, 1, 2, 3]
),
vertex_normal_indices: Some(concat_vecs!(
vec![0, 1, 0, 1],
vec![2, 2, 2, 2],
vec![2, 2, 2, 2],
vec![2, 2, 2, 2]
)),
vertex_uvs: Some(concat_vecs!(v2(7), v2(8), v2(9), v2(10))),
vertex_uv_indices: Some(concat_vecs!(
vec![0, 1, 0, 1],
vec![2, 2, 2, 2],
vec![3, 3, 3, 3],
vec![3, 3, 3, 3]
)),
..BlenderMesh::default()
}
}
fn expected_mesh_to_combine_pos_norm_uv_indices() -> BlenderMesh {
BlenderMesh {
vertex_positions: concat_vecs!(v3_x4(0, 1, 2, 3), v3_x4(0, 1, 2, 3), v3_x4(0, 1, 2, 3)),
vertex_position_indices: concat_vecs![
vec![0, 1, 2, 3,],
vec![4, 5, 6, 7],
vec![8, 9, 10, 11],
vec![8, 9, 10, 11]
],
num_vertices_in_each_face: vec![4, 4, 4, 4],
vertex_normals: concat_vecs!(v3_x4(4, 5, 4, 5), v3_x4(6, 6, 6, 6), v3_x4(6, 6, 6, 6)),
vertex_uvs: Some(concat_vecs!(
v2_x4(7, 8, 7, 8),
v2_x4(9, 9, 9, 9),
v2_x4(10, 10, 10, 10)
)),
..BlenderMesh::default()
}
}
#[test]
fn calculate_per_vertex_tangents_encountered_duplicate_data() {
let mut mesh_to_combine = BlenderMesh {
vertex_positions: concat_vecs!(
v(0),
vec![1.0, 0.0, 0.0],
vec![1.0, 1.0, 0.0],
vec![0., 1., 0.]
),
vertex_normals: concat_vecs!(v(4), v(5), v(6), v(7)),
num_vertices_in_each_face: vec![4, 4, 4, 4],
vertex_position_indices: concat_vecs!(
vec![0, 1, 2, 3],
vec![0, 1, 2, 3],
vec![0, 1, 2, 3],
vec![0, 1, 2, 3]
),
vertex_normal_indices: Some(concat_vecs!(
vec![0, 1, 2, 3],
vec![0, 1, 2, 3],
vec![0, 1, 2, 3],
vec![0, 1, 2, 3]
)),
vertex_uvs: Some(concat_vecs!(v2(0), vec![0.5, 0.0], v2(1), vec![0., 1.])),
vertex_uv_indices: Some(concat_vecs!(
vec![0, 1, 2, 3], vec![0, 1, 2, 3], vec![0, 1, 2, 3], vec![0, 1, 2, 3] )),
..BlenderMesh::default()
};
let expected_combined_mesh = BlenderMesh {
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],
vertex_position_indices: vec![0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3],
num_vertices_in_each_face: vec![4, 4, 4, 4],
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],
vertex_uvs: Some(vec![0.0, 0.0, 0.5, 0.0, 1.0, 1.0, 0.0, 1.0]),
face_tangents: Some(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,
]),
per_vertex_tangents: Some(VertexAttribute::new(
vec![8.0, 0.0, 0.0, 8.0, 0.0, 0.0, 8.0, 0.0, 0.0, 8.0, 0.0, 0.0],
AttributeSize::Three,
)),
..BlenderMesh::default()
};
let create_single_idx_config = Some(CreateSingleIndexConfig {
bone_influences_per_vertex: None,
calculate_vertex_tangents: true,
});
CombineIndicesTest {
mesh_to_combine,
expected_combined_mesh,
create_single_idx_config,
}
.test();
}
fn make_mesh_to_combine_without_uvs() -> BlenderMesh {
let start_positions = concat_vecs!(v(0), v(1), v(2), v(3));
let start_normals = concat_vecs!(v(4), v(5), v(6));
BlenderMesh {
vertex_positions: start_positions,
vertex_position_indices: vec![0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3],
num_vertices_in_each_face: vec![4, 4, 4],
vertex_normals: start_normals,
vertex_normal_indices: Some(vec![0, 1, 0, 1, 2, 2, 2, 2, 2, 2, 2, 2]),
bone_influences_per_vertex: Some(vec![3, 2, 5, 1].into()),
vertex_group_indices: Some(vec![0, 1, 2, 0, 3, 4, 5, 6, 7, 8, 11]),
vertex_group_weights: Some(vec![
0.05, 0.8, 0.15, 0.5, 0.5, 0.1, 0.2, 0.2, 0.2, 0.3, 0.999,
]),
..BlenderMesh::default()
}
}
fn make_expected_combined_mesh() -> BlenderMesh {
let end_positions = concat_vecs!(v(0), v(1), v(2), v(3), v(0), v(1), v(2), v(3));
let end_normals = concat_vecs!(v(4), v(5), v(4), v(5), v(6), v(6), v(6), v(6));
BlenderMesh {
vertex_positions: end_positions,
vertex_position_indices: vec![0, 1, 2, 3, 4, 5, 6, 7, 4, 5, 6, 7],
num_vertices_in_each_face: vec![4, 4, 4],
vertex_normals: end_normals,
bone_influences_per_vertex: Some(BoneInfluencesPerVertex::Uniform(3)),
vertex_group_indices: Some(vec![
1, 2, 0, 0, 3, 0, 8, 5, 6, 11, 0, 0, 1, 2, 0, 0, 3, 0, 8, 5, 6, 11, 0, 0,
]),
vertex_group_weights: Some(vec![
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,
0.5, 0.5, 0.0, 0.3, 0.2, 0.2, 0.999, 0.0, 0.0,
]),
..BlenderMesh::default()
}
}
}