1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
//! Blender files can have meshes such as circles, cubes, cylinders, a dragon or any other
//! 3D shape.
//!
//! A mesh can be represented as a group of vertices and data about those vertices, such as their
//! normals or UV coordinates.
//!
//! Meshes can also have metadata, such as the name of it's parent armature (useful for vertex
//! skinning).
//!
//! blender-mesh-to-json seeks to be a well tested, well documented exporter for blender mesh
//! metadata.
//!
//! You can write data to stdout or to a file. At the onset it will be geared towards @chinedufn's
//! needs - but if you have needs that aren't met feel very free to open an issue.
//!
//! @see https://docs.blender.org/manual/en/dev/modeling/meshes/introduction.html - Mesh Introduction
//! @see https://github.com/chinedufn/blender-actions-to-json - Exporting blender armatures / actions

#[macro_use]
extern crate failure;
#[macro_use]
extern crate serde_derive;
#[macro_use]
extern crate log;

pub use self::combine_indices::CreateSingleIndexConfig;
pub use self::export::*;
use crate::bone::BoneInfluencesPerVertex;
use crate::bounding_box::BoundingBox;
use crate::material::PrincipledBSDF;
use crate::vertex_data::{VertexAttribute, VertexData};
pub use material::{Channel, MaterialInput};
use serde_json;
use serde_json::Error;
use std::collections::HashMap;

mod bone;
mod bounding_box;
mod combine_indices;
mod export;
mod individual_vertex;
mod material;
mod tangent;
mod triangulate;
mod vertex_data;
mod y_up;

#[cfg(test)]
mod test_utils;

/// Something went wrong in the Blender child process that was trying to parse your mesh data.
#[derive(Debug, Fail)]
pub enum BlenderError {
    /// Errors in Blender are written to stderr. We capture the stderr from the `blender` child
    /// process that we spawned when attempting to export meshes from a `.blend` file.
    #[fail(
        display = "There was an issue while exporting meshes: Blender stderr output: {}",
        _0
    )]
    Stderr(String),
}

/// All of the data about a Blender mesh
#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[cfg_attr(test, derive(Default))]
#[serde(deny_unknown_fields)]
pub struct BlenderMesh {
    /// All of the mesh's vertices. Three items in the vector make one vertex.
    /// So indices 0, 1 and 2 are a vertex, 3, 4 and 5 are a vertex.. etc.
    /// [v1x, v1y, v1z, v2x, v2y, v2z, ...]
    pub vertex_positions: Vec<f32>,
    /// The indices within vertex positions that make up each triangle in our mesh.
    /// Three vertex position indices correspond to one triangle
    /// [0, 1, 2, 0, 2, 3, ...]
    pub vertex_position_indices: Vec<u16>,
    /// TODO: enum..? if they're all equal we replace the MyEnum::PerVertex(Vec<u8>) with MyEnum::Equal(4)
    pub num_vertices_in_each_face: Vec<u8>,
    pub vertex_normals: Vec<f32>,
    pub vertex_normal_indices: Option<Vec<u16>>,
    /// If your mesh is textured these will be all of the mesh's vertices' uv coordinates.
    /// Every vertex has two UV coordinates.
    /// [v1s, v1t, v2s, v2t, v3s, v3t]
    /// TODO: Combine vertex_uvs, vertex_uv_indices, texture_name into texture_info
    pub vertex_uvs: Option<Vec<f32>>,
    pub vertex_uv_indices: Option<Vec<u16>>,
    pub armature_name: Option<String>,
    /// TODO: When we move to single index triangulate and add new vertices give those vertices the same group indices / weights
    /// TODO: A function that trims this down to `n` weights and indices per vertex. Similar to our
    /// triangulate function
    /// TODO: Make sure that when we combine vertex indices we expand our group weights
    pub vertex_group_indices: Option<Vec<u8>>,
    pub vertex_group_weights: Option<Vec<f32>>,
    /// TODO: enum..? if they're all equal we replace the MyEnum::PerVertex(Vec<u8>) with MyEnum::Equal(4)
    bone_influences_per_vertex: Option<BoneInfluencesPerVertex>,
    pub bounding_box: BoundingBox,
    /// A map of material name (in Blender) to the material's data
    materials: HashMap<String, PrincipledBSDF>,
    /// Tangent vectors per vertex, useful for normal mapping.
    ///
    /// These get set during [`BlenderMesh.combine_indices`], if there are triangle_tangents.
    ///
    /// Useful for normal mapping.
    per_vertex_tangents: Option<VertexAttribute>,
    /// Tangent vector to the vertex, calculated using [`BlenderMesh.calculate_face_tangents`].
    ///
    /// [`BlenderMesh.calculate_face_tangents`]: struct.BlenderMesh.html#method.calculate_face_tangents
    face_tangents: Option<Vec<f32>>,
    // FIXME: Temporary move all of the vertex data above into VertexData
    // Then we no longer need default .. it'll be required
    #[serde(default)]
    vertex_data: VertexData,
}

impl BlenderMesh {
    // TODO: Delete this.. let the consumer worry about serializing / deserializing
    pub fn from_json(json_str: &str) -> Result<BlenderMesh, Error> {
        serde_json::from_str(json_str)
    }
}

/// Concatenate a series of vectors into one vector.
///
/// Useful for generating fake vertex data for unit tests.
///
/// ```ignore
/// assert_eq!(
///     concat_vecs!(vec![1, 2, 3], vec![4,5]),
///     vec![1, 2, 3, 4, 5]
/// );
/// ```
#[cfg(test)]
#[macro_export]
#[cfg(test)]
macro_rules! concat_vecs {
    ( $( $vec:expr),* ) => {
        {
            let mut concatenated_vec = Vec::new();
            $(
                concatenated_vec.append(&mut $vec.clone());
            )*
            concatenated_vec
        }
    }
}