model-converter/src/parser/mod.rs

345 lines
12 KiB
Rust

//! 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<f64>),
/// Declares a new texture coordinate.
TextureCoordinate(Vector2<f64>),
/// Declares a new normal.
Normal(Vector3<f64>),
/// 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<f64>),
/// Change the main color of the current material.
Diffuse(Vector3<f64>),
/// An unknown material instruction that will be copied into the mtl file.
UnknownMaterialInstruction(String),
/// Declares multiple faces.
Faces(Vec<Face>),
}
#[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<R: Read>(reader: R, path: &str) -> Result<Model, ParserError> {
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<R: Read>(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<Model, ParserError> {
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<R: Read>(&mut self, reader: R, path: &str) -> Result<Model, ParserError> {
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<R: Read>(&mut self, model: &mut Model, reader: R, path: &str) -> Result<(), ParserError> {
info!("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<R: Read>(&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<Element, ParserError>;
}
impl<LP: LineParser> Parser for LP {
fn priv_parse_into_model<R: Read>(&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(())
}
}