model-converter/src/parser/mod.rs

285 lines
9.5 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;
use std::fs::File;
use std::io::{BufReader, Error};
use math::vector::{Vector2, Vector3};
use model::{Face, Model, Material, 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,
/// 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.
Texture(String, String),
/// Change the main color of the current material.
Color(Vector3<f64>),
/// An unknown material instruction that will be copied into the mtl file.
UnknownMaterialInstruction(String),
}
#[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),
/// Texture arrived before creating a material.
///
/// usize is the line.
/// String is the path to the texture.
NoMaterialExist(String, usize, String),
/// Something weird happened
OtherError(String),
}
/// Detects the format from path name and parses it into the model.
pub fn parse_into_model(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;
let mut parser: Option<Box<Parser>> = match extension {
"obj" => Some(Box::new(ObjParser::new())),
"mtl" => Some(Box::new(MtlParser::new())),
_ => None,
};
if let Some(parser) = parser.as_mut() {
parser.parse_into_model(model, path)?;
Ok(())
} else {
Err(ParserError::UnknownFormat(path.to_owned()))
}
}
/// Detects the format from path name and parses it into a new model.
pub fn parse(path: &str) -> Result<Model, ParserError> {
let mut model = Model::new();
parse_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, ref n) =>
write!(f, "Missing material in {}:{}\n\tNo material were defined, can't set {} as textuure", p, l, n),
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, path: &str) -> Result<Model, ParserError> {
let mut model = Model::new();
self.parse_into_model(&mut model, path)?;
Ok(model)
}
/// Parses a file and adds its content to an already existing model.
fn parse_into_model(&mut self, model: &mut Model, path: &str) -> Result<(), ParserError> {
logln!("Parsing {}...", path);
self.priv_parse_into_model(model, 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, 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(&mut self, model: &mut Model, path: &str) -> Result<(), ParserError> {
let file = File::open(path);
if file.is_err() {
return Err(ParserError::IoError(path.to_owned(), file.err().unwrap()));
}
let file = file.unwrap();
let file = BufReader::new(&file);
let mut current_material_name = None;
use std::io::BufRead;
for (num, line) in file.lines().enumerate() {
let num = num + 1;
if line.is_err() {
return Err(ParserError::OtherError(path.to_owned()));
}
let line = line.unwrap();
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::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) => {
if current_material_name.is_none() {
return Err(ParserError::NoMaterialExist(path.to_owned(), num, texture_path));
}
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, texture_path))
}
let current_material = current_material.unwrap();
current_material.textures.insert(texture_name, texture_path);
},
Element::Color(_) => (),
Element::UnknownMaterialInstruction(s) => {
if current_material_name.is_none() {
return Err(ParserError::NoMaterialExist(path.to_owned(), num, s));
}
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, s))
}
current_material.unwrap().add_unknown_instruction(s);
}
}
}
Ok(())
}
}