#![allow(dead_code)]
#![cfg_attr(all(test, feature = "unstable"), feature(test))]
#[cfg(all(test, feature = "unstable"))] extern crate test;
use std::io::prelude::*;
use std::io::BufReader;
use std::path::Path;
use std::fs::File;
use std::collections::HashMap;
use std::str::FromStr;
#[derive(Debug, Clone)]
pub struct Mesh {
pub positions: Vec<f32>,
pub normals: Vec<f32>,
pub texcoords: Vec<f32>,
pub indices: Vec<u32>,
pub material_id: Option<usize>,
}
impl Mesh {
pub fn new(pos: Vec<f32>, norm: Vec<f32>, tex: Vec<f32>, indices: Vec<u32>, material_id: Option<usize>)
-> Mesh {
Mesh { positions: pos, normals: norm, texcoords: tex, indices: indices, material_id: material_id }
}
pub fn empty() -> Mesh {
Mesh { positions: Vec::new(), normals: Vec::new(), texcoords: Vec::new(), indices: Vec::new(),
material_id: None }
}
}
#[derive(Clone, Debug)]
pub struct Model {
pub mesh: Mesh,
pub name: String,
}
impl Model {
pub fn new(mesh: Mesh, name: String) -> Model {
Model { mesh: mesh, name: name }
}
}
#[derive(Clone, Debug)]
pub struct Material {
pub name: String,
pub ambient: [f32; 3],
pub diffuse: [f32; 3],
pub specular: [f32; 3],
pub shininess: f32,
pub dissolve: f32,
pub ambient_texture: String,
pub diffuse_texture: String,
pub specular_texture: String,
pub normal_texture: String,
pub dissolve_texture: String,
pub unknown_param: HashMap<String, String>,
}
impl Material {
pub fn empty() -> Material {
Material { name: String::new(), ambient: [0.0; 3], diffuse: [0.0; 3], specular: [0.0; 3],
shininess: 0.0, dissolve: 1.0, ambient_texture: String::new(),
diffuse_texture: String::new(), specular_texture: String::new(),
normal_texture: String::new(), dissolve_texture: String::new(),
unknown_param: HashMap::new() }
}
}
#[derive(Debug)]
pub enum LoadError {
OpenFileFailed,
ReadError,
UnrecognizedCharacter,
PositionParseError,
NormalParseError,
TexcoordParseError,
FaceParseError,
MaterialParseError,
InvalidObjectName,
GenericFailure,
}
pub type LoadResult = Result<(Vec<Model>, Vec<Material>), LoadError>;
pub type MTLLoadResult = Result<(Vec<Material>, HashMap<String, usize>), LoadError>;
#[derive(Hash, Eq, PartialEq, PartialOrd, Ord, Debug, Copy, Clone)]
struct VertexIndices {
pub v: isize,
pub vt: isize,
pub vn: isize,
}
impl VertexIndices {
fn parse(face_str: &str, pos_sz: usize, tex_sz: usize, norm_sz: usize) -> Option<VertexIndices> {
let mut indices = [-1; 3];
for i in face_str.split('/').enumerate() {
if !i.1.is_empty() {
match isize::from_str(i.1) {
Ok(x) => {
indices[i.0] =
if x < 0 {
match i.0 {
0 => x + pos_sz as isize,
1 => x + tex_sz as isize,
2 => x + norm_sz as isize,
_ => panic!("Invalid number of elements for a face (> 3)!"),
}
} else {
x - 1
};
},
Err(_) => return None,
}
}
}
Some(VertexIndices { v: indices[0], vt: indices[1], vn: indices[2] })
}
}
#[derive(Debug)]
enum Face {
Triangle(VertexIndices, VertexIndices, VertexIndices),
Quad(VertexIndices, VertexIndices, VertexIndices, VertexIndices),
Polygon(Vec<VertexIndices>),
}
fn parse_floatn<'a, T: Iterator<Item = &'a str>>(val_str: T, vals: &mut Vec<f32>, n: usize) -> bool {
let sz = vals.len();
for p in val_str {
if sz + n == vals.len() {
return true;
}
match FromStr::from_str(p) {
Ok(x) => vals.push(x),
Err(_) => return false,
}
}
sz + n == vals.len()
}
fn parse_float3<'a, T: Iterator<Item = &'a str>>(val_str: T, vals: &mut [f32; 3]) -> bool {
for (i, p) in val_str.enumerate() {
match FromStr::from_str(p) {
Ok(x) => vals[i] = x,
Err(_) => return false,
}
}
true
}
fn parse_face<'a, T: Iterator<Item = &'a str>>(face_str: T, faces: &mut Vec<Face>, pos_sz: usize, tex_sz: usize,
norm_sz: usize) -> bool {
let mut indices = Vec::new();
for f in face_str {
match VertexIndices::parse(f, pos_sz, tex_sz, norm_sz) {
Some(v) => indices.push(v),
None => return false,
}
}
match indices.len() {
3 => faces.push(Face::Triangle(indices[0], indices[1], indices[2])),
4 => faces.push(Face::Quad(indices[0], indices[1], indices[2], indices[3])),
_ => faces.push(Face::Polygon(indices)),
}
true
}
fn add_vertex(mesh: &mut Mesh, index_map: &mut HashMap<VertexIndices, u32>, vert: &VertexIndices,
pos: &Vec<f32>, texcoord: &Vec<f32>, normal: &Vec<f32>) {
match index_map.get(vert) {
Some(&i) => mesh.indices.push(i),
None => {
let v = vert.v as usize;
mesh.positions.push(pos[v * 3]);
mesh.positions.push(pos[v * 3 + 1]);
mesh.positions.push(pos[v * 3 + 2]);
if !texcoord.is_empty() && vert.vt > -1 {
let vt = vert.vt as usize;
mesh.texcoords.push(texcoord[vt * 2]);
mesh.texcoords.push(texcoord[vt * 2 + 1]);
}
if !normal.is_empty() && vert.vn > -1 {
let vn = vert.vn as usize;
mesh.normals.push(normal[vn * 3]);
mesh.normals.push(normal[vn * 3 + 1]);
mesh.normals.push(normal[vn * 3 + 2]);
}
let next = index_map.len() as u32;
mesh.indices.push(next);
index_map.insert(*vert, next);
}
}
}
fn export_faces(pos: &Vec<f32>, texcoord: &Vec<f32>, normal: &Vec<f32>, faces: &Vec<Face>,
mat_id: Option<usize>) -> Mesh {
let mut index_map = HashMap::new();
let mut mesh = Mesh::empty();
mesh.material_id = mat_id;
for f in faces {
match f {
&Face::Triangle(ref a, ref b, ref c) => {
add_vertex(&mut mesh, &mut index_map, a, pos, texcoord, normal);
add_vertex(&mut mesh, &mut index_map, b, pos, texcoord, normal);
add_vertex(&mut mesh, &mut index_map, c, pos, texcoord, normal);
},
&Face::Quad(ref a, ref b, ref c, ref d) => {
add_vertex(&mut mesh, &mut index_map, a, pos, texcoord, normal);
add_vertex(&mut mesh, &mut index_map, b, pos, texcoord, normal);
add_vertex(&mut mesh, &mut index_map, c, pos, texcoord, normal);
add_vertex(&mut mesh, &mut index_map, a, pos, texcoord, normal);
add_vertex(&mut mesh, &mut index_map, c, pos, texcoord, normal);
add_vertex(&mut mesh, &mut index_map, d, pos, texcoord, normal);
},
&Face::Polygon(ref indices) => {
let a = &indices[0];
let mut c = &indices[1];
for i in 2..indices.len() - 1 {
let b = c;
c = &indices[i];
add_vertex(&mut mesh, &mut index_map, a, pos, texcoord, normal);
add_vertex(&mut mesh, &mut index_map, b, pos, texcoord, normal);
add_vertex(&mut mesh, &mut index_map, c, pos, texcoord, normal);
}
},
}
}
mesh
}
pub fn load_obj(file_name: &Path) -> LoadResult {
let file = match File::open(file_name) {
Ok(f) => f,
Err(e) => {
println!("tobj::load_obj - failed to open {:?} due to {}", file_name, e);
return Err(LoadError::OpenFileFailed);
},
};
let mut reader = BufReader::new(file);
load_obj_buf(&mut reader, file_name.parent())
}
pub fn load_mtl(file_name: &Path) -> MTLLoadResult {
let file = match File::open(file_name) {
Ok(f) => f,
Err(e) => {
println!("tobj::load_mtl - failed to open {:?} due to {}", file_name, e);
return Err(LoadError::OpenFileFailed);
},
};
let mut reader = BufReader::new(file);
load_mtl_buf(&mut reader)
}
fn load_obj_buf<B: BufRead>(reader: &mut B, base_path: Option<&Path>) -> LoadResult {
let mut models = Vec::new();
let mut materials = Vec::new();
let mut mat_map = HashMap::new();
let mut tmp_pos = Vec::new();
let mut tmp_texcoord = Vec::new();
let mut tmp_normal = Vec::new();
let mut tmp_faces: Vec<Face> = Vec::new();
let mut name = "unnamed_object".to_string();
let mut mat_id = None;
for line in reader.lines() {
let (line, mut words) = match line {
Ok(ref line) => (&line[..], line[..].split(char::is_whitespace).filter(|s| !s.is_empty())),
Err(e) => {
println!("tobj::load_obj - failed to read line due to {}", e);
return Err(LoadError::ReadError);
},
};
match words.next() {
Some("#") | None => continue,
Some("v") => {
if !parse_floatn(words, &mut tmp_pos, 3) {
return Err(LoadError::PositionParseError);
}
},
Some("vt") => {
if !parse_floatn(words, &mut tmp_texcoord, 2) {
return Err(LoadError::TexcoordParseError);
}
},
Some("vn") => {
if !parse_floatn(words, &mut tmp_normal, 3) {
return Err(LoadError::NormalParseError);
}
},
Some("f") => {
if !parse_face(words, &mut tmp_faces, tmp_pos.len() / 3, tmp_texcoord.len() / 2,
tmp_normal.len() / 3) {
return Err(LoadError::FaceParseError);
}
},
Some("o") | Some("g") => {
if !name.is_empty() && !tmp_faces.is_empty() {
models.push(Model::new(export_faces(&tmp_pos, &tmp_texcoord, &tmp_normal,
&tmp_faces, mat_id), name));
tmp_faces.clear();
}
name = line[1..].trim().to_string();
if name.is_empty() {
return Err(LoadError::InvalidObjectName);
}
},
Some("mtllib") => {
if let Some(mtllib) = words.next() {
let mat_file = match base_path {
Some(bp) => bp.join(mtllib),
None => Path::new(mtllib).to_path_buf(),
};
match load_mtl(mat_file.as_path()) {
Ok((mats, map)) => {
let mat_offset = materials.len();
for m in mats {
materials.push(m);
}
for m in map {
mat_map.insert(m.0, m.1 + mat_offset);
}
},
Err(e) => return Err(e),
}
} else {
return Err(LoadError::MaterialParseError);
}
},
Some("usemtl") => {
if let Some(mat_name) = words.next() {
match mat_map.get(mat_name) {
Some(m) => mat_id = Some(*m),
None => {
mat_id = None;
println!("Warning: Object {} refers to unfound material: {}", name, mat_name);
}
}
} else {
return Err(LoadError::MaterialParseError);
}
},
Some(_) => {},
}
}
if !name.is_empty() {
models.push(Model::new(export_faces(&tmp_pos, &tmp_texcoord, &tmp_normal, &tmp_faces, mat_id), name));
}
Ok((models, materials))
}
fn load_mtl_buf<B: BufRead>(reader: &mut B) -> MTLLoadResult {
let mut materials = Vec::new();
let mut mat_map = HashMap::new();
let mut cur_mat = Material::empty();
for line in reader.lines() {
let (line, mut words) = match line {
Ok(ref line) => (&line[..], line[..].split(char::is_whitespace).filter(|s| !s.is_empty())),
Err(e) => {
println!("tobj::load_obj - failed to read line due to {}", e);
return Err(LoadError::ReadError);
},
};
match words.next() {
Some("#") | None => continue,
Some("newmtl") => {
if !cur_mat.name.is_empty() {
mat_map.insert(cur_mat.name.clone(), materials.len());
materials.push(cur_mat);
}
cur_mat = Material::empty();
cur_mat.name = line[6..].trim().to_string();
if cur_mat.name.is_empty() {
return Err(LoadError::InvalidObjectName);
}
},
Some("Ka") => {
if !parse_float3(words, &mut cur_mat.ambient) {
return Err(LoadError::MaterialParseError);
}
},
Some("Kd") => {
if !parse_float3(words, &mut cur_mat.diffuse) {
return Err(LoadError::MaterialParseError);
}
},
Some("Ks") => {
if !parse_float3(words, &mut cur_mat.specular) {
return Err(LoadError::MaterialParseError);
}
},
Some("Ns") => {
if let Some(p) = words.next() {
match FromStr::from_str(p) {
Ok(x) => cur_mat.shininess = x,
Err(_) => return Err(LoadError::MaterialParseError),
}
} else {
return Err(LoadError::MaterialParseError);
}
},
Some("d") => {
if let Some(p) = words.next() {
match FromStr::from_str(p) {
Ok(x) => cur_mat.dissolve = x,
Err(_) => return Err(LoadError::MaterialParseError),
}
} else {
return Err(LoadError::MaterialParseError);
}
},
Some("map_Ka") => {
match words.next() {
Some(tex) => cur_mat.ambient_texture = tex.to_string(),
None => return Err(LoadError::MaterialParseError),
}
},
Some("map_Kd") => {
match words.next() {
Some(tex) => cur_mat.diffuse_texture = tex.to_string(),
None => return Err(LoadError::MaterialParseError),
}
},
Some("map_Ks") => {
match words.next() {
Some(tex) => cur_mat.specular_texture = tex.to_string(),
None => return Err(LoadError::MaterialParseError),
}
},
Some("map_Ns") => {
match words.next() {
Some(tex) => cur_mat.normal_texture = tex.to_string(),
None => return Err(LoadError::MaterialParseError),
}
},
Some("map_d") => {
match words.next() {
Some(tex) => cur_mat.dissolve_texture = tex.to_string(),
None => return Err(LoadError::MaterialParseError),
}
},
Some(unknown) => {
if !unknown.is_empty() {
let param = line[unknown.len()..].trim().to_string();
cur_mat.unknown_param.insert(unknown.to_string(), param);
}
},
}
}
if !cur_mat.name.is_empty() {
mat_map.insert(cur_mat.name.clone(), materials.len());
materials.push(cur_mat);
}
Ok((materials, mat_map))
}
pub fn print_model_info(models: &Vec<Model>, materials: &Vec<Material>) {
println!("# of models: {}", models.len());
println!("# of materials: {}", materials.len());
for (i, m) in models.iter().enumerate() {
let mesh = &m.mesh;
println!("model[{}].name = \'{}\'", i, m.name);
println!("model[{}].mesh.material_id = {:?}", i, mesh.material_id);
println!("Size of model[{}].indices: {}", i, mesh.indices.len());
for f in 0..mesh.indices.len() / 3 {
println!(" idx[{}] = {}, {}, {}.", f, mesh.indices[3 * f], mesh.indices[3 * f + 1],
mesh.indices[3 * f + 2]);
}
println!("model[{}].vertices: {}", i, mesh.positions.len() / 3);
println!("model[{}].normals: {}", i, mesh.normals.len() / 3);
println!("model[{}].texcoords: {}", i, mesh.texcoords.len() / 2);
assert!(mesh.positions.len() % 3 == 0);
assert!(mesh.normals.len() % 3 == 0);
assert!(mesh.texcoords.len() % 2 == 0);
for v in 0..mesh.positions.len() / 3 {
println!(" v[{}] = ({}, {}, {})", v, mesh.positions[3 * v], mesh.positions[3 * v + 1],
mesh.positions[3 * v + 2]);
if !mesh.normals.is_empty() {
println!(" vn[{}] = ({}, {}, {})", v, mesh.normals[3 * v], mesh.normals[3 * v + 1],
mesh.normals[3 * v + 2]);
}
if !mesh.texcoords.is_empty() {
println!(" vt[{}] = ({}, {})", v, mesh.texcoords[2 * v], mesh.texcoords[2 * v + 1]);
}
}
}
print_material_info(materials);
}
pub fn print_material_info(materials: &Vec<Material>) {
for (i, m) in materials.iter().enumerate() {
println!("material[{}].name = \'{}\'", i, m.name);
println!(" material.Ka = ({}, {}, {})", m.ambient[0], m.ambient[1], m.ambient[2]);
println!(" material.Kd = ({}, {}, {})", m.diffuse[0], m.diffuse[1], m.diffuse[2]);
println!(" material.Ks = ({}, {}, {})", m.specular[0], m.specular[1], m.specular[2]);
println!(" material.Ns = {}", m.shininess);
println!(" material.d = {}", m.dissolve);
println!(" material.map_Ka = {}", m.ambient_texture);
println!(" material.map_Kd = {}", m.diffuse_texture);
println!(" material.map_Ks = {}", m.specular_texture);
println!(" material.map_Ns = {}", m.normal_texture);
println!(" material.map_d = {}", m.dissolve_texture);
for (k, v) in &m.unknown_param {
println!(" material.{} = {}", k, v);
}
}
}
#[cfg(all(test, feature = "unstable"))]
mod benches {
use test::Bencher;
use std::path::Path;
use super::load_obj;
#[bench]
fn bench_cornell(b: &mut Bencher) {
let path = Path::new("cornell_box.obj");
b.iter(|| {
let m = load_obj(&path);
assert!(m.is_ok());
m.is_ok()
});
}
}