Inital commit coming from dash-3d
This commit is contained in:
@@ -0,0 +1,279 @@
|
||||
//! 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)
|
||||
}
|
||||
|
||||
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() {
|
||||
|
||||
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(())
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
//! Module containing the parser for material files from Wavefront OBJ (mtl files).
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use parser::{Element, ParserError, LineParser};
|
||||
|
||||
/// Wavefront material file format parser.
|
||||
pub struct MtlParser {
|
||||
|
||||
}
|
||||
|
||||
impl MtlParser {
|
||||
|
||||
/// Creates a MtlParser.
|
||||
pub fn new() -> MtlParser {
|
||||
MtlParser {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impl LineParser for MtlParser {
|
||||
fn parse_line(&mut self, _line_number: usize, line: &str, path: &str) -> Result<Element, ParserError> {
|
||||
|
||||
let mut root = PathBuf::from(path);
|
||||
root.pop();
|
||||
|
||||
let mut split = line.split_whitespace();
|
||||
|
||||
if let Some(first) = split.nth(0) {
|
||||
|
||||
let first = first.trim();
|
||||
|
||||
match first {
|
||||
|
||||
// Ignore comments
|
||||
"#" => Ok(Element::None),
|
||||
|
||||
// Starts a new material
|
||||
"newmtl" => Ok(Element::NewMaterial(split.nth(0).unwrap().to_owned())),
|
||||
|
||||
map if map.starts_with("map_") => {
|
||||
|
||||
let mut full_path = root.clone();
|
||||
full_path.push(split.nth(0).unwrap().to_owned());
|
||||
Ok(Element::Texture(first.to_owned(), full_path.to_str().unwrap().to_owned()))
|
||||
|
||||
},
|
||||
|
||||
// The keyword is not empty and unexpected
|
||||
// We don't do this : we simply return an unknown material instruction
|
||||
// key if key.len() != 0 => {
|
||||
// Err(ParserError::UnexpectedKeyword(path.to_owned(), line_number, key.to_owned()))
|
||||
// },
|
||||
|
||||
// Empty string
|
||||
"" => Ok(Element::None),
|
||||
|
||||
// Unknown instruction
|
||||
_ => Ok(Element::UnknownMaterialInstruction(line.to_owned())),
|
||||
|
||||
}
|
||||
} else {
|
||||
Ok(Element::None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,220 @@
|
||||
//! Module containing the Wavefront OBJ parser.
|
||||
|
||||
use parser::{Element, ParserError, LineParser};
|
||||
use math::vector::{Vector2, Vector3};
|
||||
use model::{FaceVertex, Face};
|
||||
|
||||
/// The wavefront OBJ format parser.
|
||||
pub struct ObjParser {
|
||||
|
||||
}
|
||||
|
||||
impl ObjParser {
|
||||
|
||||
/// Creates a new parser.
|
||||
pub fn new() -> ObjParser {
|
||||
ObjParser {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses a certain number of value in a line, and returns a Vec containing the values.
|
||||
///
|
||||
/// Will return an error if the number of values expected is incorrect.
|
||||
fn parse_values(&self, line_number: usize, line: &str, path: &str, number_of_values: usize) -> Result<Vec<f64>, ParserError> {
|
||||
|
||||
let mut split = line.split_whitespace();
|
||||
split.next();
|
||||
|
||||
let mut ret = vec![];
|
||||
|
||||
for elt in split {
|
||||
if let Ok(value) = elt.parse::<f64>() {
|
||||
ret.push(value);
|
||||
} else {
|
||||
return Err(ParserError::ParseNumberError(path.to_owned(), line_number, elt.to_owned()));
|
||||
}
|
||||
}
|
||||
if ret.len() == number_of_values {
|
||||
Ok(ret)
|
||||
} else {
|
||||
Err(ParserError::IncorrectNumberOfParameters(path.to_owned(), line_number, number_of_values, ret.len()))
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses an obj vertex line.
|
||||
///
|
||||
/// ```
|
||||
/// # use dash_3d_exporter::math::vector::Vector3;
|
||||
/// # use dash_3d_exporter::parser::obj::ObjParser;
|
||||
/// # use dash_3d_exporter::parser::Element::Vertex;
|
||||
/// let obj = ObjParser::new();
|
||||
/// let vertex = obj.parse_vertex(0, "v 1 2 3", "file.obj").unwrap();
|
||||
/// assert_eq!(vertex, Vertex(Vector3::new(1.0, 2.0, 3.0)));
|
||||
/// ```
|
||||
pub fn parse_vertex(&self, line_number: usize, line: &str, path: &str) -> Result<Element, ParserError> {
|
||||
|
||||
let values = self.parse_values(line_number, line, path, 3)?;
|
||||
Ok(Element::Vertex(Vector3::<f64>::new(
|
||||
values[0],
|
||||
values[1],
|
||||
values[2],
|
||||
)))
|
||||
}
|
||||
|
||||
/// Parses an obj texture coordinate line.
|
||||
///
|
||||
/// ```
|
||||
/// # use dash_3d_exporter::math::vector::Vector2;
|
||||
/// # use dash_3d_exporter::parser::obj::ObjParser;
|
||||
/// # use dash_3d_exporter::parser::Element::TextureCoordinate;
|
||||
/// let obj = ObjParser::new();
|
||||
/// let texture_coordinate = obj.parse_texture_coordinate(0, "vt 1 2", "file.obj").unwrap();
|
||||
/// assert_eq!(texture_coordinate, TextureCoordinate(Vector2::new(1.0, 2.0)));
|
||||
/// ```
|
||||
pub fn parse_texture_coordinate(&self, line_number: usize, line: &str, path: &str) -> Result<Element, ParserError> {
|
||||
|
||||
let values = self.parse_values(line_number, line, path, 2)?;
|
||||
Ok(Element::TextureCoordinate(Vector2::<f64>::new(
|
||||
values[0],
|
||||
values[1],
|
||||
)))
|
||||
}
|
||||
|
||||
/// Parses an obj normal line.
|
||||
///
|
||||
/// ```
|
||||
/// # use dash_3d_exporter::math::vector::Vector3;
|
||||
/// # use dash_3d_exporter::parser::obj::ObjParser;
|
||||
/// # use dash_3d_exporter::parser::Element::Normal;
|
||||
/// let obj = ObjParser::new();
|
||||
/// let normal = obj.parse_normal(0, "vn 1 2 3", "file.obj").unwrap();
|
||||
/// assert_eq!(normal, Normal(Vector3::new(1.0, 2.0, 3.0)));
|
||||
/// ```
|
||||
pub fn parse_normal(&self, line_number: usize, line: &str, path: &str) -> Result<Element, ParserError> {
|
||||
|
||||
let values = self.parse_values(line_number, line, path, 3)?;
|
||||
Ok(Element::Normal(Vector3::<f64>::new(
|
||||
values[0],
|
||||
values[1],
|
||||
values[2],
|
||||
)))
|
||||
|
||||
}
|
||||
|
||||
/// Parses an obj face line.
|
||||
///
|
||||
/// The index is changed to start from 0.
|
||||
/// ```
|
||||
/// # use dash_3d_exporter::math::vector::Vector3;
|
||||
/// # use dash_3d_exporter::parser::obj::ObjParser;
|
||||
/// # use dash_3d_exporter::parser::Element;
|
||||
/// # use dash_3d_exporter::model::{Face, FaceVertex};
|
||||
/// let obj = ObjParser::new();
|
||||
/// let face1 = obj.parse_face(0, "f 1 2 3", "file.obj").unwrap();
|
||||
/// let face2 = Element::Face(Face::new(
|
||||
/// FaceVertex::new(0, None, None),
|
||||
/// FaceVertex::new(1, None, None),
|
||||
/// FaceVertex::new(2, None, None),
|
||||
/// ));
|
||||
/// assert_eq!(face1, face2);
|
||||
///
|
||||
/// let face1 = obj.parse_face(0, "f 1/1/1 2/2/2 3/3/3", "file.obj").unwrap();
|
||||
/// let face2 = Element::Face(Face::new(
|
||||
/// FaceVertex::new(0, Some(0), Some(0)),
|
||||
/// FaceVertex::new(1, Some(1), Some(1)),
|
||||
/// FaceVertex::new(2, Some(2), Some(2)),
|
||||
/// ));
|
||||
/// assert_eq!(face1, face2);
|
||||
/// ```
|
||||
|
||||
pub fn parse_face(&self, line_number: usize, line: &str, path: &str) -> Result<Element, ParserError> {
|
||||
|
||||
let mut split = line.split_whitespace();
|
||||
split.next();
|
||||
|
||||
let mut face_vertices = vec![];
|
||||
|
||||
for elt in split {
|
||||
|
||||
let mut resplit = elt.split('/');
|
||||
let vertex_string = resplit.nth(0).unwrap();
|
||||
let vertex = vertex_string.parse::<usize>();
|
||||
|
||||
if vertex.is_err() {
|
||||
return Err(ParserError::ParseNumberError(path.to_owned(), line_number, vertex_string.to_owned()));
|
||||
}
|
||||
|
||||
let vertex = vertex.ok().unwrap() - 1;
|
||||
|
||||
let texture_coordinate_string = resplit.nth(0);
|
||||
let mut texture_coordinate = None;
|
||||
|
||||
if let Some(texture_coordinate_string) = texture_coordinate_string {
|
||||
if texture_coordinate_string.len() != 0 {
|
||||
let texture_coordinate_index = texture_coordinate_string.parse::<usize>();
|
||||
|
||||
if texture_coordinate_index.is_err() {
|
||||
return Err(ParserError::ParseNumberError(
|
||||
path.to_owned(),line_number, texture_coordinate_string.to_owned()
|
||||
));
|
||||
} else {
|
||||
texture_coordinate = Some(texture_coordinate_index.ok().unwrap() - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let normal_string = resplit.nth(0);
|
||||
let mut normal = None;
|
||||
|
||||
if let Some(normal_string) = normal_string {
|
||||
if normal_string.len() != 0 {
|
||||
let normal_index = normal_string.parse::<usize>();
|
||||
|
||||
if normal_index.is_err() {
|
||||
return Err(ParserError::ParseNumberError(
|
||||
path.to_owned(), line_number, normal_string.to_owned()
|
||||
));
|
||||
} else {
|
||||
normal = Some(normal_index.ok().unwrap() - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
face_vertices.push(FaceVertex::new(vertex, texture_coordinate, normal));
|
||||
|
||||
}
|
||||
|
||||
if face_vertices.len() != 3 {
|
||||
Err(ParserError::TooManyVertices(path.to_owned(), line_number, face_vertices.len()))
|
||||
} else {
|
||||
Ok(Element::Face(Face::new(face_vertices[0], face_vertices[1], face_vertices[2])))
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impl LineParser for ObjParser {
|
||||
fn parse_line(&mut self, line_number:usize, line: &str, path: &str) -> Result<Element, ParserError> {
|
||||
|
||||
let mut split = line.split_whitespace();
|
||||
|
||||
if let Some(first) = split.nth(0) {
|
||||
match first {
|
||||
"v" => self.parse_vertex(line_number, line, path),
|
||||
"vt" => self.parse_texture_coordinate(line_number, line, path),
|
||||
"vn" => self.parse_normal(line_number, line, path),
|
||||
"usemtl" => Ok(Element::UseMaterial(split.nth(0).unwrap().to_owned())),
|
||||
"f" => self.parse_face(line_number, line, path),
|
||||
"#" | "g" | "s" | "o" | "mtllib" => Ok(Element::None),
|
||||
key if key.len() != 0 => Err(ParserError::UnexpectedKeyword(path.to_owned(), line_number, key.to_owned())),
|
||||
_ => Ok(Element::None),
|
||||
}
|
||||
} else {
|
||||
Ok(Element::None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user