Inital commit coming from dash-3d
This commit is contained in:
commit
bccb83b538
|
@ -0,0 +1,4 @@
|
|||
|
||||
/target/
|
||||
**/*.rs.bk
|
||||
Cargo.lock
|
|
@ -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" }
|
|
@ -0,0 +1,8 @@
|
|||
extern crate num;
|
||||
|
||||
#[macro_use]
|
||||
extern crate verbose_log;
|
||||
|
||||
pub mod math;
|
||||
pub mod model;
|
||||
pub mod parser;
|
|
@ -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);
|
|
@ -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 {}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue