//! Module that contains all the logic for parsing 3D files. pub mod obj; pub mod mtl; use std::fmt; use std::path::{Path, PathBuf}; use std::fs::File; use std::io::{BufReader, Error, Read}; use math::vector::{Vector2, Vector3}; use model::face::Face; use model::material::Material; use model::{Model, ModelError}; /// A 3D element. /// /// All elements that exists in 3D and that can appear in a 3D format file should have its variant /// in this enum. #[derive(PartialEq, Debug)] pub enum Element { /// An empty element (correspond to an empty line for example). None, /// Declares the use of a material file. MaterialLib(String), /// Changes the material used for the next faces. UseMaterial(String), /// Declares a new vertex. Vertex(Vector3), /// Declares a new texture coordinate. TextureCoordinate(Vector2), /// Declares a new normal. Normal(Vector3), /// Declares a new face. Face(Face), /// Declares a new material. NewMaterial(String), /// Changes the texture of the current material. /// /// First string is the name of the map. /// Second string is the path to the image file. /// Vector3 is the size of the texture. Texture(String, String, Vector3), /// Change the main color of the current material. Diffuse(Vector3), /// An unknown material instruction that will be copied into the mtl file. UnknownMaterialInstruction(String), /// Declares multiple faces. Faces(Vec), } #[derive(Debug)] /// An error while parsing a file. pub enum ParserError { /// An std::io::Error when opening the file. IoError(String, Error), /// The model format is unknown. UnknownFormat(String), /// A keyword was expected but something else arrived. UnexpectedKeyword(String, usize, String), /// Failed to parse a number. ParseNumberError(String, usize, String), /// Failed to parse a line. /// /// First usize is line. /// Second usize is expected. /// Thirs usize is got. IncorrectNumberOfParameters(String, usize, usize, usize), /// A face contains too many vertices. /// /// Only 3 vertices face are supported. /// /// First usize is line. /// Second usize is the number of vertices in the face. TooManyVertices(String, usize, usize), /// A face references a vertex / tex coord / normal that does not exist. /// /// First usize is line. /// Second usize is the id of the vertex. /// Third usize is the size of the array. IndexOutOfBound(String, usize, usize, usize), /// Material already exists. /// /// usize is the line. /// String is the name of the material that is already defined. MaterialAlreadyExists(String, usize, String), /// Some material information arrived before creating a material. /// /// usize is the line. NoMaterialExist(String, usize), /// No path after a map NoPathAfterMap(String, usize), /// Unexpected token UnexpectedToken(String, usize, String), /// Something weird happened OtherError(String), } /// Parses a content from a reader, guess the file type from path, and return the model. pub fn parse(reader: R, path: &str) -> Result { let mut model = Model::new(); parse_into_model(reader, path, &mut model)?; Ok(model) } /// Parses a file and append the result to the model passed as parameter. pub fn parse_file_into_model(path: &str, model: &mut Model) -> Result<(), ParserError> { let file = match File::open(path) { Ok(file) => file, Err(e) => return Err(ParserError::IoError(path.to_owned(), e)), }; let bufreader = BufReader::new(file); parse_into_model(bufreader, path, model)?; Ok(()) } /// Detects the format from path name and parses it into the model. pub fn parse_into_model(reader: R, path: &str, model: &mut Model) -> Result<(), ParserError> { let extension = Path::new(path).extension().unwrap().to_str().unwrap(); use self::obj::ObjParser; use self::mtl::MtlParser; match extension { "obj" => ObjParser::new().parse_into_model(model, reader, path)?, "mtl" => MtlParser::new().parse_into_model(model, reader, path)?, _ => return Err(ParserError::UnknownFormat(path.to_owned())), } Ok(()) } /// Detects the format from path name and parses it into a new model. pub fn parse_file(path: &str) -> Result { let mut model = Model::new(); parse_file_into_model(path, &mut model)?; Ok(model) } impl fmt::Display for ParserError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { ParserError::IoError(ref s, ref e) => write!(f, "Error opening file {}: {}", s, e), ParserError::UnknownFormat(ref s) => write!(f, "Unknown format for file {}", s), ParserError::UnexpectedKeyword(ref p, n, ref s) => write!(f, "Unexpected keyword in {}:{}:\n\t{}", p, n, s), ParserError::ParseNumberError(ref p, n, ref s) => write!(f, "Could not parse number in {}:{}:\n\t{}", p, n, s), ParserError::IncorrectNumberOfParameters(ref p, l, e, r) => write!(f, "Wrong number of parameters in {}:{}:\n\texpected {}, got {}", p, l, e, r), ParserError::TooManyVertices(ref p, l, s) => write!(f, "Too many faces in {}:{}:\n\t{}", p, l, s), ParserError::IndexOutOfBound(ref p, l, i, s) => write!(f, "Index out of bound in {}:{}:\n\tsize is {} but got {}", p, l, i, s), ParserError::MaterialAlreadyExists(ref p, l, ref n) => write!(f, "Redecralation of material in {}:{}:\n\t{} already exists", p, n, l), ParserError::NoMaterialExist(ref p, l) => write!(f, "Missing material in {}:{}\n\tNo material were defined, can't set attribute", p, l), ParserError::NoPathAfterMap(ref p, l) => write!(f, "Missing path for map in {}:{}", p, l), ParserError::UnexpectedToken(ref p, l, ref t) => write!(f, "Unexpected token {} in {}:{}", t, p, l), ParserError::OtherError(ref p) => write!(f, "Something weird happened in {}...", p), } } } /// Anything that can parse a 3D file and create a 3D model from it. pub trait Parser { /// Parses a file and adds its content into an empty model and returns the model. fn parse(&mut self, reader: R, path: &str) -> Result { let mut model = Model::new(); self.parse_into_model(&mut model, reader, path)?; Ok(model) } /// Parses a file and adds its content to an already existing model. fn parse_into_model(&mut self, model: &mut Model, reader: R, path: &str) -> Result<(), ParserError> { logln!("Parsing {}...", path); self.priv_parse_into_model(model, reader, path) } /// Parses a file and adds its content to an already existing model. /// /// This is the method you should implement for you format parser. fn priv_parse_into_model(&mut self, model: &mut Model, reader: R, path: &str) -> Result<(), ParserError>; } /// Anything that can parse a 3D file line by line. /// /// This should not be used for binary files for instance. pub trait LineParser { /// Parses a single line of a file fn parse_line(&mut self, line_number: usize, line: &str, filename: &str) -> Result; } impl Parser for LP { fn priv_parse_into_model(&mut self, model: &mut Model, reader: R, path: &str) -> Result<(), ParserError> { let file = BufReader::new(reader); let mut current_material_name = None; use std::io::BufRead; for (num, line) in file.lines().enumerate() { let num = num + 1; let line = match line { Ok(x) => x, Err(e) => return Err(ParserError::IoError(path.to_owned(), e)), }; let element = self.parse_line(num, &line, &path)?; match element { Element::None => (), Element::UseMaterial(m) => model.change_part(Some(m)), Element::Vertex(v) => model.vertices.push(v), Element::TextureCoordinate(v) => model.texture_coordinates.push(v), Element::Normal(v) => model.normals.push(v), Element::MaterialLib(ref material_path) => { // Append material_path to path let mut path = PathBuf::from(path); path.pop(); path.push(material_path); if let Err(err) = parse_file_into_model(path.to_str().unwrap(), model) { return Err(err); } }, Element::Face(f) => { if let Err(ModelError::IndexError(index, size)) = model.add_face(f) { return Err(ParserError::IndexOutOfBound(path.to_owned(), num, index, size)); } }, Element::NewMaterial(m) => { if model.materials.contains_key(&m) { return Err(ParserError::MaterialAlreadyExists(path.to_owned(), num, m)); } else { let new_material = Material::new(&m); current_material_name = Some(m.clone()); model.materials.insert(m, new_material); } }, Element::Texture(texture_name, texture_path, size) => { if current_material_name.is_none() { return Err(ParserError::NoMaterialExist(path.to_owned(), num)); } let material_name = current_material_name.as_ref().unwrap().clone(); let current_material = model.materials.get_mut(&material_name); if current_material.is_none() { return Err(ParserError::NoMaterialExist(path.to_owned(), num)) } let current_material = current_material.unwrap(); current_material.textures.insert(texture_name, (texture_path, size)); }, Element::Diffuse(v) => { if current_material_name.is_none() { return Err(ParserError::NoMaterialExist(path.to_owned(), num)); } let material_name = current_material_name.as_ref().unwrap().clone(); let current_material = model.materials.get_mut(&material_name); if current_material.is_none() { return Err(ParserError::NoMaterialExist(path.to_owned(), num)); } let current_material = current_material.unwrap(); current_material.diffuse = v; }, Element::UnknownMaterialInstruction(s) => { if current_material_name.is_none() { return Err(ParserError::NoMaterialExist(path.to_owned(), num)); } let material_name = current_material_name.as_ref().unwrap().clone(); let current_material = model.materials.get_mut(&material_name); if current_material.is_none() { return Err(ParserError::NoMaterialExist(path.to_owned(), num)) } current_material.unwrap().add_unknown_instruction(s); }, Element::Faces(faces) => { for f in faces { if let Err(ModelError::IndexError(index, size)) = model.add_face(f) { return Err(ParserError::IndexOutOfBound(path.to_owned(), num, index, size)); } } } } } Ok(()) } }