//! 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, Debug, PartialEq)] pub struct Vertex { vertex: [f64; 3], tex_coords: [f64; 2], normal: [f64; 3], } implement_vertex!(Vertex, vertex, tex_coords, normal); #[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, /// The index of the normal, None if there is no normal. pub normal: Option, } impl FaceVertex { /// Creates a new face vertex from its attributes. pub fn new(vertex: usize, texture_coordinate: Option, normal: Option) -> 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, /// Id of the face, number of the face in the original file. pub id: Option, } 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, /// Instructions that are unknown. /// /// They might be necessary for the export though. pub unknown_instructions: Vec, } 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, /// List of all the faces in the part. pub faces: Vec, } impl Part { /// Creates a empty part from its material name. pub fn new(material_name: Option) -> 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); } pub fn build_shape(&self, vertices: &Vec>, tex_coords: &Vec>, normals: &Vec>) -> Vec { let mut shape = vec![]; for face in &self.faces { for &&v in &[&face.a, &face.b, &face.c] { shape.push(Vertex { vertex: vertices[v.vertex].into(), tex_coords: tex_coords[v.texture_coordinate.unwrap()].into(), normal: normals[v.normal.unwrap()].into(), }); } } shape } } /// A 3D model. /// /// It contains all the data needed to do a rendering. pub struct Model { /// List of all vertices. pub vertices: Vec>, /// List of all texture coordinates. pub texture_coordinates: Vec>, /// List of all normals. pub normals: Vec>, /// Map associating the name of a material to the real material. pub materials: HashMap, /// The list of parts of the model. pub parts: Vec, /// The list of faces, in the order of the file pub faces: Vec, /// The part that is currently selected. current_part_index: Option, } /// 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 { parse(path) } /// Computes the area of a 3D face of the model. pub fn face_area(&self, f: &Face) -> f64 { self.face_vertex_area(&f.a, &f.b, &f.c) } /// Computes the 3D area of a face from 3 face vertices. pub fn face_vertex_area(&self, a: &FaceVertex, b: &FaceVertex, c: &FaceVertex) -> f64 { let v1 = self.vertices[a.vertex]; let v2 = self.vertices[b.vertex]; let v3 = self.vertices[c.vertex]; Vector3::area(v1, v2, v3) } /// Returns the name of the current material, i.e. the material corresponding to the current /// part. pub fn current_material_name(&self) -> Option { 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) { 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) { 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 { 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) } }