Inital commit coming from dash-3d

This commit is contained in:
Thomas Forgione 2018-02-23 12:04:26 +01:00
commit bccb83b538
No known key found for this signature in database
GPG Key ID: C75CD416BD1FFCE1
10 changed files with 1402 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
/target/
**/*.rs.bk
Cargo.lock

8
Cargo.toml Normal file
View File

@ -0,0 +1,8 @@
[package]
name = "model-converter"
version = "0.1.0"
authors = ["Thomas Forgione <thomas@tforgione.fr>"]
[dependencies]
num = "*"
verbose-log = { path = "../verbose-log" }

8
src/lib.rs Normal file
View File

@ -0,0 +1,8 @@
extern crate num;
#[macro_use]
extern crate verbose_log;
pub mod math;
pub mod model;
pub mod parser;

65
src/math/bounding_box.rs Normal file
View File

@ -0,0 +1,65 @@
//! Module containing the bounding box struct.
use math::Number;
macro_rules! make_bounding_box {
($name: ident, $vector: ident, $size: expr) => {
use math::vector::$vector;
/// Represents a bounding box.
pub struct $name<T> {
min: $vector<T>,
max: $vector<T>,
}
impl<T: PartialOrd + Copy + Clone> $name<T> {
/// Creates a bounding box from its min and max coordinates.
pub fn new(min: $vector<T>, max: $vector<T>) -> $name<T> {
$name {
min: min,
max: max,
}
}
/// Enlarges the bounding box so it contains the point passed as parameter.
pub fn add_point(&mut self, point: &$vector<T>) {
for i in 0..$size {
if point[i] < self.min[i] {
self.min[i] = point[i];
}
if point[i] > self.max[i] {
self.max[i] = point[i];
}
}
}
/// Returns the minimum value of the bounding box.
pub fn min(&self) -> $vector<T> {
self.min
}
/// Returns the maximum value of the bounding box.
pub fn max(&self) -> $vector<T> {
self.max
}
}
impl<T: Number> $name<T> {
/// Scales a bounding box from its center.
pub fn scale(&mut self, factor: T) {
let diag = self.max - self.min;
self.min -= diag * factor;
self.max += diag * factor;
}
}
}
}
make_bounding_box!(BoundingBox2, Vector2, 2);
make_bounding_box!(BoundingBox3, Vector3, 3);
make_bounding_box!(BoundingBox4, Vector4, 4);

33
src/math/mod.rs Normal file
View File

@ -0,0 +1,33 @@
//! Module containing all the math structs and functions.
pub mod vector;
pub mod bounding_box;
use std::ops::{Add, AddAssign, Sub, SubAssign, Mul, MulAssign, Div, DivAssign};
use num::{Zero, One};
/// A number.
///
/// This trait is used to implement the operator traits on the vector struct.
pub trait Number: Copy + Clone + PartialOrd + Zero + One
+ Add<Output = Self> + AddAssign
+ Sub<Output = Self> + SubAssign
+ Mul<Output = Self> + MulAssign
+ Div<Output = Self> + DivAssign
{
}
impl Number for i16 {}
impl Number for i32 {}
impl Number for i64 {}
impl Number for isize {}
impl Number for u16 {}
impl Number for u32 {}
impl Number for u64 {}
impl Number for usize {}
impl Number for f32 {}
impl Number for f64 {}

361
src/math/vector.rs Normal file
View File

@ -0,0 +1,361 @@
//! Module containing the vector struct.
use std::ops::{Add, AddAssign, Sub, SubAssign, Mul, MulAssign, Div, DivAssign, Index, IndexMut};
use num::Zero;
use num::Float;
use math::Number;
macro_rules! make_vector {
( $name: ident,
$number: expr,
( $( $t: ident) , * ),
$( ($x: ident, $x_mut: ident, $y: expr) ), * ) => {
/// The vector struct.
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct $name<T> {
data: [T; $number],
}
impl<T> $name<T> {
/// Creates a vector from its coordinates.
pub fn new( $( $x: T ) , *) -> $name<T> {
$name::<T> {
data: [ $( $x ) , *],
}
}
/// Returns the length of the vector.
pub fn len(&self) -> usize {
return $number;
}
}
impl<T: Copy + Clone> Into<($( $t ) ,* )> for $name<T> {
fn into(self) -> ($( $t ) ,* ) {
( $( self.data[$y] ), *)
}
}
impl<T: Number> Zero for $name<T> {
fn zero() -> $name<T> {
$name::<T> {
data: [ T::zero(); $number ],
}
}
fn is_zero(&self) -> bool {
for x in &self.data {
if ! x.is_zero() {
return false;
}
}
true
}
}
impl<T: Copy + Clone> $name<T> {
$(
/// Get the coordinate of the vector.
pub fn $x(&self) -> T {
self.data[$y]
}
)*
}
impl<T: Number> $name<T> {
/// Computes the dot product between two vectors.
pub fn dot(&self, rhs: $name<T>) -> T {
$( self.data[$y] * rhs.data[$y] + )* T::zero()
}
}
impl<T: Number> $name<T> {
/// Computes the iso barycenter of a list of vectors.
pub fn barycenter(vectors: &[&$name<T>]) -> $name<T> {
use num::Zero;
let mut sum = $name::<T>::zero();
let mut denom = T::zero();
for v in vectors.iter() {
sum += **v;
denom += T::one();
}
sum / denom
}
/// Computes the square of the 2-norm of the vector.
pub fn norm2(&self) -> T {
let mut res = T::zero();
for x in self.data.iter() {
res += *x * *x;
}
res
}
}
impl<T: Number + Float> $name<T> {
/// Computes the 2-norm of the vector
pub fn norm(&self) -> T {
self.norm2().sqrt()
}
}
impl<T: Number> Add for $name<T> {
type Output = $name<T>;
fn add(self, rhs: $name<T>) -> Self::Output {
$name::<T>::new( $( self.data[$y] + rhs.data[$y] ) , *)
}
}
impl<T: Number> AddAssign for $name<T> {
fn add_assign(&mut self, rhs: $name<T>) {
$(
self.data[$y] += rhs.data[$y];
)*
}
}
impl<T: Number> Sub for $name<T> {
type Output = $name<T>;
fn sub(self, rhs: $name<T>) -> Self::Output {
$name::<T>::new( $( self.data[$y] - rhs.data[$y] ) , *)
}
}
impl<T: Number> SubAssign for $name<T> {
fn sub_assign(&mut self, rhs: $name<T>) {
$(
self.data[$y] -= rhs.data[$y];
)*
}
}
impl<T: Number> Mul<T> for $name<T> {
type Output = $name<T>;
fn mul(self, rhs: T) -> Self::Output {
$name::<T>::new( $( self.data[$y] * rhs ), *)
}
}
impl<T: Number> MulAssign<T> for $name<T> {
fn mul_assign(&mut self, rhs: T) {
$(
self.data[$y] *= rhs;
)*
}
}
impl<T: Number> Div<T> for $name<T> {
type Output = $name<T>;
fn div(self, rhs: T) -> Self::Output {
$name::<T>::new( $( self.data[$y] / rhs ), *)
}
}
impl<T: Number> DivAssign<T> for $name<T> {
fn div_assign(&mut self, rhs: T) {
$(
self.data[$y] /= rhs;
)*
}
}
impl<T> Index<usize> for $name<T> {
type Output = T;
fn index(&self, index: usize) -> &T {
&self.data[index]
}
}
impl<T> IndexMut<usize> for $name<T> {
fn index_mut(&mut self, index: usize) -> &mut T {
&mut self.data[index]
}
}
// impl<T: PartialEq> PartialEq for $name<T> {
// fn eq(&self, other: &$name<T>) -> bool {
// for (x1, x2) in self.data.iter().zip(other.data.iter()) {
// if x1 != x2 {
// return false;
// }
// }
// true
// }
// }
};
}
make_vector!(Vector2, 2, (T, T), (x, x_mut, 0), (y, y_mut, 1));
make_vector!(Vector3, 3, (T, T, T), (x, x_mut, 0), (y, y_mut, 1), (z, z_mut, 2));
make_vector!(Vector4, 4, (T, T, T, T), (x, x_mut, 0), (y, y_mut, 1), (z, z_mut, 2), (t, t_mut, 3));
impl Vector2<f32> {
pub fn orthogonal(&self) -> Vector2<f32> {
Vector2::new(
self.y(),
self.x() * -1.0,
)
}
}
impl Vector2<i32> {
pub fn orthogonal(&self) -> Vector2<i32> {
Vector2::new(
self.y(),
self.x() * -1,
)
}
}
impl<T: Number> Vector3<T> {
/// Computes the cross product between two vectors.
pub fn cross_product(&self, rhs: Vector3<T>) -> Vector3<T> {
Vector3::new(
self.data[1] * rhs.data[2] - self.data[2] * rhs.data[1],
self.data[2] * rhs.data[0] - self.data[0] * rhs.data[2],
self.data[0] * rhs.data[1] - self.data[1] * rhs.data[0]
)
}
}
impl Vector3<f64> {
/// Computes the area of a triangle in 3 dimensions.
pub fn area(a: Vector3<f64>, b: Vector3<f64>, c: Vector3<f64>) -> f64 {
let v1 = b - a;
let v2 = c - a;
let cross = v1.cross_product(v2);
0.5 * cross.norm()
}
}
#[cfg(test)]
mod test {
macro_rules! assert_delta {
($x:expr, $y:expr, $d:expr) => {
if !($x - $y < $d || $y - $x < $d) { panic!(); }
}
}
mod vector2 {
use math::vector::Vector2;
#[test]
fn create() {
let v = Vector2::new(0, 1);
assert_eq!(v.data[0], 0);
assert_eq!(v.data[1], 1);
}
#[test]
fn getters() {
let v = Vector2::new(0, 1);
assert_eq!(v.x(), 0);
assert_eq!(v.y(), 1);
}
#[test]
fn index() {
let v = Vector2::new(0, 1);
assert_eq!(v[0], 0);
assert_eq!(v[1], 1);
}
#[test]
fn index_mut() {
let mut v = Vector2::new(0, 1);
v[0] = 1;
v[1] = 0;
assert_eq!(v.data[0], 1);
assert_eq!(v.data[1], 0);
}
#[test]
fn dot_1() {
let v1 = Vector2::new(0, 1);
let v2 = Vector2::new(1, 0);
assert_eq!(v1.dot(v2), 0);
}
#[test]
fn dot_2() {
let v1 = Vector2::new(1, 1);
let v2 = Vector2::new(1, 1);
assert_eq!(v1.dot(v2), 2);
}
#[test]
fn dot_3() {
let v1 = Vector2::new(1, 2);
let v2 = Vector2::new(2, 2);
assert_eq!(v1.dot(v2), 6);
}
#[test]
fn add_1() {
let v1 = Vector2::new(0, 1);
let v2 = Vector2::new(1, 0);
let v = v1 + v2;
assert_eq!(v.data[0], 1);
assert_eq!(v.data[1], 1);
}
#[test]
fn add_2() {
let v1 = Vector2::new(1, 1);
let v2 = Vector2::new(1, 1);
let v = v1 + v2;
assert_eq!(v.data[0], 2);
assert_eq!(v.data[1], 2);
}
#[test]
fn add_3() {
let v1 = Vector2::new(1, 2);
let v2 = Vector2::new(2, 2);
let v = v1 + v2;
assert_eq!(v.data[0], 3);
assert_eq!(v.data[1], 4);
}
#[test]
fn norm2_1() {
let v = Vector2::new(1,2);
assert_eq!(v.norm2(), 5);
}
#[test]
fn norm_1() {
let v = Vector2::new(3.0, 4.0);
assert_delta!(v.norm(), 5.0, 0.001);
}
#[test]
fn norm_2() {
use num::Float;
let v = Vector2::new(1.0, 1.0);
assert_delta!(v.norm(), 2.0.sqrt(), 0.001);
}
}
}

355
src/model/mod.rs Normal file
View File

@ -0,0 +1,355 @@
//! Module containg the struct for storing 3D model
use std::collections::HashMap;
use std::fmt;
use parser::{parse, ParserError};
use math::vector::{Vector2, Vector3};
use math::bounding_box::BoundingBox3;
#[derive(Copy, Clone, PartialEq)]
/// The indices needed for each vertex of a face.
pub struct FaceVertex {
/// The index of the vertex.
pub vertex: usize,
/// The index of the texture coordinate, None if there is no texture coordinate.
pub texture_coordinate: Option<usize>,
/// The index of the normal, None if there is no normal.
pub normal: Option<usize>,
}
impl FaceVertex {
/// Creates a new face vertex from its attributes.
pub fn new(vertex: usize, texture_coordinate: Option<usize>, normal: Option<usize>) -> FaceVertex {
FaceVertex {
vertex: vertex,
texture_coordinate: texture_coordinate,
normal: normal,
}
}
}
impl fmt::Debug for FaceVertex {
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
let texture_coordinate = if let Some(tc) = self.texture_coordinate {
tc.to_string()
} else {
"".to_owned()
};
let normal = if let Some(n) = self.normal {
n.to_string()
} else {
"".to_owned()
};
write!(formatter, "{}/{}/{}", self.vertex, texture_coordinate, normal)
}
}
#[derive(Clone, PartialEq, Debug)]
/// A 3D Face.
///
/// It contains exactly 3 face vertices, and the name of the corresponding material.
pub struct Face {
/// The first vertex of the face.
pub a: FaceVertex,
/// The second vertex of the face.
pub b: FaceVertex,
/// The third vertex of the face.
pub c: FaceVertex,
/// The name of the material, None if no material is specified.
pub material_name: Option<String>,
/// Id of the face, number of the face in the original file.
pub id: Option<usize>,
}
impl Face {
/// Creates a new face from its attributes.
///
/// Material name defaults to None.
pub fn new(a: FaceVertex, b: FaceVertex, c: FaceVertex) -> Face {
Face {
a: a,
b: b,
c: c,
material_name: None,
id: None,
}
}
}
/// A 3D material.
pub struct Material {
/// The name of the material.
pub name: String,
/// Map linking each texture map to its file path.
pub textures: HashMap<String, String>,
/// Instructions that are unknown.
///
/// They might be necessary for the export though.
pub unknown_instructions: Vec<String>,
}
impl Material {
/// Creates a material from its name, without texture.
pub fn new(name: &str) -> Material {
Material {
name: name.to_owned(),
textures: HashMap::new(),
unknown_instructions: vec![],
}
}
/// Adds a unknown instruction into the material.
pub fn add_unknown_instruction(&mut self, instruction: String) {
self.unknown_instructions.push(instruction);
}
}
/// A part of a 3D model.
///
/// It contains a list of faces that correspond to the same material.
pub struct Part {
/// Name of the corresponding material.
pub material_name: Option<String>,
/// List of all the faces in the part.
pub faces: Vec<Face>,
}
impl Part {
/// Creates a empty part from its material name.
pub fn new(material_name: Option<String>) -> Part {
Part {
material_name: material_name,
faces: vec![],
}
}
/// Adds a face to the part.
///
/// The face material will be changed according to the part material.
pub fn add_face(&mut self, f: Face) {
let mut f = f;
f.material_name = self.material_name.clone();
self.faces.push(f);
}
}
/// A 3D model.
///
/// It contains all the data needed to do a rendering.
pub struct Model {
/// List of all vertices.
pub vertices: Vec<Vector3<f64>>,
/// List of all texture coordinates.
pub texture_coordinates: Vec<Vector2<f64>>,
/// List of all normals.
pub normals: Vec<Vector3<f64>>,
/// Map associating the name of a material to the real material.
pub materials: HashMap<String, Material>,
/// The list of parts of the model.
pub parts: Vec<Part>,
/// The list of faces, in the order of the file
pub faces: Vec<Face>,
/// The part that is currently selected.
current_part_index: Option<usize>,
}
/// Error when adding a face to a model.
pub enum ModelError {
/// Index (first usize) received but greater than size (second usize).
IndexError(usize, usize),
}
impl Model {
/// Creates an empty model.
pub fn new() -> Model {
Model {
vertices: vec![],
texture_coordinates: vec![],
normals: vec![],
materials: HashMap::new(),
parts: vec![],
faces: vec![],
current_part_index: None,
}
}
/// Creates a model from a file.
pub fn from(path: &str) -> Result<Model, ParserError> {
parse(path)
}
/// Returns the name of the current material, i.e. the material corresponding to the current
/// part.
pub fn current_material_name(&self) -> Option<String> {
if let Some(p) = self.current_part() {
p.material_name.clone()
} else {
None
}
}
/// Changes the part in order to select a specific material.
///
/// If no part with such material exists, it will be created.
pub fn change_part(&mut self, material_name: Option<String>) {
self.current_part_index = None;
for (index, part) in self.parts.iter().enumerate() {
if part.material_name == material_name {
self.current_part_index = Some(index);
}
}
if self.current_part_index.is_none() {
// Create a new part
self.create_part(material_name.clone());
self.current_part_index = Some(self.parts.len() - 1);
}
}
/// Creates a part with a specific material.
pub fn create_part(&mut self, material_name: Option<String>) {
self.parts.push(Part::new(material_name));
}
/// Returns the current selected part.
///
/// Returns None if the model contains no part.
pub fn current_part(&self) -> Option<&Part> {
if let Some(index) = self.current_part_index {
Some(&self.parts[index])
} else {
None
}
}
/// Returns the current selected part.
///
/// Returns None if the model contains no part.
pub fn current_part_mut(&mut self) -> Option<&mut Part> {
if let Some(index) = self.current_part_index {
Some(&mut self.parts[index])
} else {
None
}
}
/// Adds a face to the current part of the model.
///
/// Returns a ModelError if the face has out of bounds face vertices.
pub fn add_face(&mut self, f: Face) -> Result<(), ModelError> {
// Check that the indices are correct
for face_vertex in [&f.a, &f.b, &f.c].iter() {
if face_vertex.vertex >= self.vertices.len() {
return Err(ModelError::IndexError(face_vertex.vertex, self.vertices.len()));
}
if let Some(tc) = face_vertex.texture_coordinate {
if tc >= self.texture_coordinates.len() {
return Err(ModelError::IndexError(tc, self.texture_coordinates.len()));
}
}
if let Some(n) = face_vertex.normal {
if n >= self.normals.len() {
return Err(ModelError::IndexError(n, self.normals.len()));
}
}
}
let material_name = self.current_material_name();
self.change_part(material_name.clone());
// Add the id and the material name
let mut f = f;
f.id = Some(self.faces.len());
f.material_name = material_name;
self.current_part_mut().unwrap().add_face(f.clone());
self.faces.push(f);
Ok(())
}
/// Computes the bounding box of the model.
///
/// This function works on the vertices. If there is an unused vertex, it will still count in
/// the bounding box.
pub fn bounding_box(&self) -> BoundingBox3<f64> {
let mut bbox = BoundingBox3::new(self.vertices[0].clone(), self.vertices[0].clone());
for vertex in &self.vertices {
bbox.add_point(&vertex);
}
bbox
}
/// Computes stats on the area of the faces
///
/// Returns a pair containing the average and the standard deviation.
pub fn get_area_stats(&self) -> (f64, f64) {
let mut areas = vec![];
let mut avg = 0.0;
for (index, face) in self.faces.iter().enumerate() {
let area = Vector3::area(
self.vertices[face.a.vertex],
self.vertices[face.b.vertex],
self.vertices[face.c.vertex],
);
areas.push((index, area));
avg += area;
}
avg /= areas.len() as f64;
let mut sd = 0.0;
for &(_, area) in &areas {
sd += (area - avg) * (area - avg);
}
sd = (sd / areas.len() as f64).sqrt();
(avg, sd)
}
}

279
src/parser/mod.rs Normal file
View File

@ -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(())
}
}

69
src/parser/mtl.rs Normal file
View File

@ -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)
}
}
}

220
src/parser/obj.rs Normal file
View File

@ -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)
}
}
}