Inital commit coming from dash-3d
This commit is contained in:
		
						commit
						bccb83b538
					
				
							
								
								
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,4 @@ | ||||
| 
 | ||||
| /target/ | ||||
| **/*.rs.bk | ||||
| Cargo.lock | ||||
							
								
								
									
										8
									
								
								Cargo.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								Cargo.toml
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										8
									
								
								src/lib.rs
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										65
									
								
								src/math/bounding_box.rs
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										33
									
								
								src/math/mod.rs
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										361
									
								
								src/math/vector.rs
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										355
									
								
								src/model/mod.rs
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										279
									
								
								src/parser/mod.rs
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										69
									
								
								src/parser/mtl.rs
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										220
									
								
								src/parser/obj.rs
									
									
									
									
									
										Normal 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) | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user