285 lines
9.5 KiB
Rust
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(())
|
|
}
|
|
|
|
}
|