#[deny(missing_docs)]
#[macro_use]
extern crate failure;
#[macro_use]
extern crate serde_derive;
use serde_json;
use serde_json::Error;
use std::collections::HashMap;
use crate::bounding_box::BoundingBox;
mod combine_indices;
mod bounding_box;
mod y_up;
#[derive(Debug, Fail)]
pub enum BlenderError {
#[fail(
display = "There was an issue while exporting meshes: Blender stderr output: {}",
_0
)]
Stderr(String),
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[cfg_attr(test, derive(Default))]
#[serde(deny_unknown_fields)]
pub struct BlenderMesh {
pub vertex_positions: Vec<f32>,
pub vertex_position_indices: Vec<u16>,
pub num_vertices_in_each_face: Vec<u8>,
pub vertex_normals: Vec<f32>,
pub vertex_normal_indices: Option<Vec<u16>>,
pub vertex_uvs: Option<Vec<f32>>,
pub vertex_uv_indices: Option<Vec<u16>>,
pub texture_name: Option<String>,
pub armature_name: Option<String>,
pub vertex_group_indices: Option<Vec<u8>>,
pub vertex_group_weights: Option<Vec<f32>>,
pub num_groups_for_each_vertex: Option<Vec<u8>>, pub bounding_box: BoundingBox
}
impl BlenderMesh {
pub fn from_json(json_str: &str) -> Result<BlenderMesh, Error> {
serde_json::from_str(json_str)
}
}
impl BlenderMesh {
pub fn triangulate(&mut self) {
let mut triangulated_position_indices = vec![];
let mut triangulated_face_vertex_counts = vec![];
let mut face_pointer = 0;
for num_verts_in_face in self.num_vertices_in_each_face.iter() {
match num_verts_in_face {
&3 => {
triangulated_face_vertex_counts.push(3);
triangulated_position_indices.push(self.vertex_position_indices[face_pointer]);
triangulated_position_indices
.push(self.vertex_position_indices[face_pointer + 1]);
triangulated_position_indices
.push(self.vertex_position_indices[face_pointer + 2]);
face_pointer += 3;
}
&4 => {
triangulated_face_vertex_counts.push(3);
triangulated_face_vertex_counts.push(3);
triangulated_position_indices.push(self.vertex_position_indices[face_pointer]);
triangulated_position_indices
.push(self.vertex_position_indices[face_pointer + 1]);
triangulated_position_indices
.push(self.vertex_position_indices[face_pointer + 2]);
triangulated_position_indices.push(self.vertex_position_indices[face_pointer]);
triangulated_position_indices
.push(self.vertex_position_indices[face_pointer + 2]);
triangulated_position_indices
.push(self.vertex_position_indices[face_pointer + 3]);
face_pointer += 4;
}
_ => {
panic!("blender-mesh currently only supports triangulating faces with 3 or 4 vertices");
}
}
}
self.vertex_position_indices = triangulated_position_indices;
self.num_vertices_in_each_face = triangulated_face_vertex_counts;
}
}
impl BlenderMesh {
pub fn set_groups_per_vertex(&mut self, count: u8) {
let mut normalized_group_indices = vec![];
let mut normalized_group_weights = vec![];
let mut current_index: u32 = 0;
{
let indices = self.vertex_group_indices.as_mut().unwrap();
let weights = self.vertex_group_weights.as_mut().unwrap();
self.num_groups_for_each_vertex = Some(
self.num_groups_for_each_vertex
.as_ref()
.unwrap()
.iter()
.map(|group_count| {
let mut vertex_indices = vec![];
let mut vertex_weights = vec![];
for index in current_index..(current_index + *group_count as u32) {
vertex_indices.push(index);
vertex_weights.push(weights[index as usize]);
}
vertex_weights.sort_by(|a, b| b.partial_cmp(a).unwrap());
vertex_indices.sort_by(|a, b| {
weights[*b as usize]
.partial_cmp(&weights[*a as usize])
.unwrap()
});
let mut vertex_indices: Vec<u8> = vertex_indices
.iter()
.map(|i| indices[*i as usize])
.collect();
vertex_indices.resize(count as usize, 0);
vertex_weights.resize(count as usize, 0.0);
normalized_group_indices.append(&mut vertex_indices);
normalized_group_weights.append(&mut vertex_weights);
current_index += *group_count as u32;
count
}).collect(),
);
}
self.vertex_group_indices = Some(normalized_group_indices);
self.vertex_group_weights = Some(normalized_group_weights);
}
}
pub type MeshNamesToData = HashMap<String, BlenderMesh>;
pub type FilenamesToMeshes = HashMap<String, MeshNamesToData>;
pub fn parse_meshes_from_blender_stdout(
blender_stdout: &str,
) -> Result<FilenamesToMeshes, failure::Error> {
let mut filenames_to_meshes = HashMap::new();
let mut index = 0;
while let Some((filename_to_mesh, next_start_index)) =
find_first_mesh_after_index(blender_stdout, index)
{
filenames_to_meshes.extend(filename_to_mesh);
index = next_start_index;
}
Ok(filenames_to_meshes)
}
fn find_first_mesh_after_index(
blender_stdout: &str,
index: usize,
) -> Option<(FilenamesToMeshes, usize)> {
let blender_stdout = &blender_stdout[index as usize..];
if let Some(mesh_start_index) = blender_stdout.find("START_MESH_JSON") {
let mut filenames_to_meshes = HashMap::new();
let mut mesh_name_to_data = HashMap::new();
let mesh_end_index = blender_stdout.find("END_MESH_JSON").unwrap();
let mesh_data = &blender_stdout[mesh_start_index..mesh_end_index];
let mut lines = mesh_data.lines();
let first_line = lines.next().unwrap();
let mesh_filename: Vec<&str> = first_line.split(" ").collect();
let mesh_filename = mesh_filename[1].to_string();
let mesh_name = first_line.split(" ").last().unwrap().to_string();
let mesh_data: String = lines.collect();
let mesh_data: BlenderMesh = serde_json::from_str(&mesh_data).unwrap();
mesh_name_to_data.insert(mesh_name, mesh_data);
filenames_to_meshes.insert(mesh_filename, mesh_name_to_data);
return Some((filenames_to_meshes, index + mesh_end_index + 1));
}
return None;
}
#[cfg(test)]
mod tests {
use super::*;
macro_rules! concat_vecs {
( $( $vec:expr),* ) => {
{
let mut concatenated_vec = Vec::new();
$(
concatenated_vec.append(&mut $vec.clone());
)*
concatenated_vec
}
}
}
struct CombineIndicesTest {
mesh_to_combine: BlenderMesh,
expected_combined_mesh: BlenderMesh,
}
fn test_combine_indices(mut combine_indices_test: CombineIndicesTest) {
combine_indices_test
.mesh_to_combine
.combine_vertex_indices();
let combined_mesh = combine_indices_test.mesh_to_combine;
assert_eq!(combined_mesh, combine_indices_test.expected_combined_mesh);
}
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]),
num_groups_for_each_vertex: Some(vec![3, 2, 5, 1]),
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,
num_groups_for_each_vertex: Some(vec![3, 2, 5, 1, 3, 2, 5, 1]),
vertex_group_indices: Some(vec![
0, 1, 2, 0, 3, 4, 5, 6, 7, 8, 11, 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, 0.05, 0.8, 0.15, 0.5,
0.5, 0.1, 0.2, 0.2, 0.2, 0.3, 0.999,
]),
..BlenderMesh::default()
}
}
#[test]
fn combine_pos_norm_indices() {
let mesh_to_combine = make_mesh_to_combine_without_uvs();
let expected_combined_mesh = make_expected_combined_mesh();
test_combine_indices(CombineIndicesTest {
mesh_to_combine,
expected_combined_mesh,
});
}
#[test]
fn combine_pos_norm_uv_indices() {
let mesh_to_combine = 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]
)),
num_groups_for_each_vertex: None,
vertex_group_indices: None,
vertex_group_weights: None,
..BlenderMesh::default()
};
let expected_combined_mesh = 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_combine_indices(CombineIndicesTest {
mesh_to_combine,
expected_combined_mesh,
});
}
#[test]
fn triangulate_faces() {
let mut start_mesh = BlenderMesh {
vertex_position_indices: vec![0, 1, 2, 3, 4, 5, 6, 7],
num_vertices_in_each_face: vec![4, 4],
..BlenderMesh::default()
};
start_mesh.triangulate();
let triangulated_mesh = start_mesh;
let expected_mesh = BlenderMesh {
vertex_position_indices: vec![0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7],
num_vertices_in_each_face: vec![3, 3, 3, 3],
..BlenderMesh::default()
};
assert_eq!(triangulated_mesh, expected_mesh);
}
#[test]
fn set_joints_per_vert() {
let mut start_mesh = BlenderMesh {
vertex_group_indices: Some(vec![0, 2, 3, 4, 0, 1, 3, 2]),
num_groups_for_each_vertex: Some(vec![1, 3, 4]),
vertex_group_weights: Some(vec![1.0, 0.5, 0.2, 0.3, 0.6, 0.15, 0.1, 0.15]),
..BlenderMesh::default()
};
start_mesh.set_groups_per_vertex(3);
let three_joints_per_vert = start_mesh;
let expected_mesh = BlenderMesh {
vertex_group_indices: Some(vec![0, 0, 0, 2, 4, 3, 0, 1, 2]),
num_groups_for_each_vertex: Some(vec![3, 3, 3]),
vertex_group_weights: Some(vec![1.0, 0.0, 0.0, 0.5, 0.3, 0.2, 0.6, 0.15, 0.15]),
..BlenderMesh::default()
};
assert_eq!(three_joints_per_vert, expected_mesh);
}
fn v(val: u8) -> Vec<f32> {
vec![val as f32, val as f32, val as f32]
}
fn v2_x4(vert1: u8, vert2: u8, vert3: u8, vert4: u8) -> Vec<f32> {
concat_vecs!(v2(vert1), v2(vert2), v2(vert3), v2(vert4))
}
fn v3_x4(v1: u8, v2: u8, v3: u8, v4: u8) -> Vec<f32> {
concat_vecs!(v(v1), v(v2), v(v3), v(v4))
}
fn v2(val: u8) -> Vec<f32> {
vec![val as f32, val as f32]
}
}