diff --git a/src/model/mod.rs b/src/model/mod.rs index 0d28d08..99a184e 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -5,11 +5,12 @@ pub mod material; use std::collections::HashMap; use std::collections::hash_map::Entry; +use std::rc::Rc; use glium::VertexBuffer; use glium::texture::SrgbTexture2d; -use parser::{parse, ParserError}; +use parser::{parse_file, ParserError}; use model::face::{FaceVertex, Face}; use model::material::Material; @@ -38,11 +39,14 @@ pub struct Part { pub material_name: Option, /// List of all the faces in the part. - pub faces: Vec, + faces: Vec, /// VertexBuffer for rendering. vertex_buffer: Option>, + /// True if the faces have been changed since the last time the buffer was built. + needs_update: bool, + } impl Part { @@ -53,6 +57,7 @@ impl Part { material_name: material_name, faces: vec![], vertex_buffer: None, + needs_update: true, } } @@ -63,6 +68,7 @@ impl Part { let mut f = f; f.material_name = self.material_name.clone(); self.faces.push(f); + self.needs_update = true; } /// Returns a reference to the vertex buffer. @@ -70,6 +76,11 @@ impl Part { &self.vertex_buffer } + /// Returns a reference to the faces. + pub fn faces(&self) -> &Vec { + &self.faces + } + } @@ -91,7 +102,7 @@ pub struct Model { pub materials: HashMap, /// Map associating the name of a texture and the real texture. - pub textures: HashMap, + pub textures: HashMap>, /// The list of parts of the model. pub parts: Vec, @@ -127,10 +138,57 @@ impl Model { } } - /// Merges the model passed as parameter into the current model. - pub fn merge(&mut self, other: Model) { + /// Copies the materials and textures from the other model into self. + pub fn merge_material_and_textures(&mut self, other: &Model) { + // Merge the materials + for (name, material) in &other.materials { + let entry = self.materials.entry(name.clone()); + if let Entry::Occupied(_) = entry { + eprintln!("Warning: materials merged but sharing the same name"); + // Return error + } else { + entry.or_insert(material.clone()); + } + } + + // Merge the textures + for (name, texture) in &other.textures { + let entry = self.textures.entry(name.clone()); + if let Entry::Occupied(_) = entry { + eprintln!("Warning: textures merged but sharing the same name"); + // return Error; + } else { + entry.or_insert(texture.clone()); + } + } + } + + /// Merges the model passed as parameter into new parts of the current model. + pub fn merge_in_new_parts(&mut self, other: Model) { let mut other = other; + // Merge the materials + for (name, material) in other.materials { + let entry = self.materials.entry(name); + if let Entry::Occupied(_) = entry { + eprintln!("Warning: materials merged but sharing the same name"); + // Return error + } else { + entry.or_insert(material); + } + } + + // Merge the textures + for (name, texture) in other.textures { + let entry = self.textures.entry(name); + if let Entry::Occupied(_) = entry { + eprintln!("Warning: textures merged but sharing the same name"); + // return Error; + } else { + entry.or_insert(texture); + } + } + let vertex_offset = self.vertices.len(); let tex_coord_offset = self.texture_coordinates.len(); let normal_offset = self.normals.len(); @@ -161,9 +219,17 @@ impl Model { new_part.faces.push(face.clone()); self.faces.push(face.clone()); } + new_part.needs_update = true; self.parts.push(new_part); } + + } + + /// Merges the model passed as parameter into existing parts of the current model. + pub fn merge_in_existing_parts(&mut self, other: Model) { + let mut other = other; + // Merge the materials for (name, material) in other.materials { let entry = self.materials.entry(name); @@ -185,11 +251,51 @@ impl Model { entry.or_insert(texture); } } + + let vertex_offset = self.vertices.len(); + let tex_coord_offset = self.texture_coordinates.len(); + let normal_offset = self.normals.len(); + let face_offset = self.faces.len(); + + // Merge the elements + self.vertices.append(&mut other.vertices); + self.texture_coordinates.append(&mut other.texture_coordinates); + self.normals.append(&mut other.normals); + + // Merge the parts and faces + for part in &mut other.parts { + self.change_part(part.material_name.clone()); + let mut new_faces = vec![]; + { + let new_part = self.current_part_mut().unwrap(); + for face in &mut part.faces { + for fv in &mut [&mut face.a, &mut face.b, &mut face.c] { + fv.vertex += vertex_offset; + if let Some(tex_coord) = fv.texture_coordinate { + fv.texture_coordinate = Some(tex_coord + tex_coord_offset); + } + if let Some(normal) = fv.normal { + fv.normal = Some(normal + normal_offset); + } + } + if let Some(id) = face.id { + face.id = Some(id + face_offset); + } + face.material_name = new_part.material_name.clone(); + new_part.faces.push(face.clone()); + new_faces.push(face); + } + } + + for face in new_faces { + self.faces.push(face.clone()); + } + } } /// Creates a model from a file. pub fn from(path: &str) -> Result { - parse(path) + parse_file(path) } /// Computes the area of a 3D face of the model. @@ -380,7 +486,7 @@ impl Model { /// Creates only non-existant vertex buffers, doesn't update the old ones. pub fn build_new_vertex_buffers(&mut self, renderer: &Renderer) { for part in &mut self.parts { - if part.vertex_buffer.is_none() { + if part.vertex_buffer.is_none() || part.needs_update { Model::build_vertex_buffer_for_part( &self.vertices, &self.texture_coordinates, @@ -400,7 +506,7 @@ impl Model { renderer: &Renderer) { let mut vertex_buffer = vec![]; - for face in &part.faces { + for face in part.faces() { for &&v in &[&face.a, &face.b, &face.c] { let vertex = vertices[v.vertex].into(); let tex_coord = if let Some(tex_index) = v.texture_coordinate { @@ -424,6 +530,7 @@ impl Model { } part.vertex_buffer = Some(renderer.build_vertex_buffer(&vertex_buffer)); + part.needs_update = false; } /// Builds the textures of all materials. @@ -438,17 +545,22 @@ impl Model { if let Some(path) = material.textures.get("map_Kd") { let texture = renderer.make_texture(path); // Don't need to insert multiple times the same texture - self.textures.entry(path.to_owned()).or_insert(texture); + self.textures.entry(path.to_owned()).or_insert(Rc::new(texture)); }; } /// Returns the rendering texture. - pub fn get_texture_by_name(&self, name: &str) -> Option<&SrgbTexture2d> { + pub fn get_texture_by_name(&self, name: &str) -> Option<&Rc> { self.textures.get(name) } + /// Returns the material. + pub fn get_material_by_name(&self, name: &str) -> Option<&Material> { + self.materials.get(name) + } + /// Returns a ref mut to the table of textures. - pub fn textures_mut(&mut self) -> &mut HashMap { + pub fn textures_mut(&mut self) -> &mut HashMap> { &mut self.textures } } diff --git a/src/parser/mod.rs b/src/parser/mod.rs index ed5d659..2021ba7 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -6,7 +6,7 @@ pub mod mtl; use std::fmt; use std::path::{Path, PathBuf}; use std::fs::File; -use std::io::{BufReader, Error}; +use std::io::{BufReader, Error, Read}; use math::vector::{Vector2, Vector3}; use model::face::Face; use model::material::Material; @@ -112,31 +112,46 @@ pub enum ParserError { OtherError(String), } +/// Parses a content from a reader, guess the file type from path, and return the model. +pub fn parse(reader: R, path: &str) -> Result { + let mut model = Model::new(); + parse_into_model(reader, path, &mut model)?; + Ok(model) +} + +/// Parses a file and append the result to the model passed as parameter. +pub fn parse_file_into_model(path: &str, model: &mut Model) -> Result<(), ParserError> { + let file = match File::open(path) { + Ok(file) => file, + Err(e) => return Err(ParserError::IoError(path.to_owned(), e)), + }; + + let bufreader = BufReader::new(file); + parse_into_model(bufreader, path, model)?; + + Ok(()) +} + /// Detects the format from path name and parses it into the model. -pub fn parse_into_model(path: &str, model: &mut Model) -> Result<(), ParserError> { +pub fn parse_into_model(reader: R, 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> = 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())) + match extension { + "obj" => ObjParser::new().parse_into_model(model, reader, path)?, + "mtl" => MtlParser::new().parse_into_model(model, reader, path)?, + _ => return Err(ParserError::UnknownFormat(path.to_owned())), } + + Ok(()) } /// Detects the format from path name and parses it into a new model. -pub fn parse(path: &str) -> Result { +pub fn parse_file(path: &str) -> Result { let mut model = Model::new(); - parse_into_model(path, &mut model)?; + parse_file_into_model(path, &mut model)?; Ok(model) } @@ -171,22 +186,22 @@ impl fmt::Display for ParserError { 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 { + fn parse(&mut self, reader: R, path: &str) -> Result { let mut model = Model::new(); - self.parse_into_model(&mut model, path)?; + self.parse_into_model(&mut model, reader, 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> { + fn parse_into_model(&mut self, model: &mut Model, reader: R, path: &str) -> Result<(), ParserError> { logln!("Parsing {}...", path); - self.priv_parse_into_model(model, path) + self.priv_parse_into_model(model, reader, path) } /// Parses a file and adds its content to an already existing model. /// /// This is the method you should implement for you format parser. - fn priv_parse_into_model(&mut self, model: &mut Model, path: &str) -> Result<(), ParserError>; + fn priv_parse_into_model(&mut self, model: &mut Model, reader: R, path: &str) -> Result<(), ParserError>; } /// Anything that can parse a 3D file line by line. @@ -200,16 +215,9 @@ pub trait LineParser { impl Parser for LP { - fn priv_parse_into_model(&mut self, model: &mut Model, path: &str) -> Result<(), ParserError> { + fn priv_parse_into_model(&mut self, model: &mut Model, reader: R, 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 file = BufReader::new(reader); let mut current_material_name = None; @@ -218,11 +226,10 @@ impl Parser for LP { let num = num + 1; - if line.is_err() { - return Err(ParserError::OtherError(path.to_owned())); - } - - let line = line.unwrap(); + let line = match line { + Ok(x) => x, + Err(e) => return Err(ParserError::IoError(path.to_owned(), e)), + }; let element = self.parse_line(num, &line, &path)?; @@ -238,7 +245,7 @@ impl Parser for LP { let mut path = PathBuf::from(path); path.pop(); path.push(material_path); - if let Err(err) = parse_into_model(path.to_str().unwrap(), model) { + if let Err(err) = parse_file_into_model(path.to_str().unwrap(), model) { return Err(err); } }, diff --git a/src/programs/viewer.rs b/src/programs/viewer.rs index 6011e37..4c4fa48 100644 --- a/src/programs/viewer.rs +++ b/src/programs/viewer.rs @@ -20,7 +20,7 @@ use glium::glutin::VirtualKeyCode; use model_converter::scene::Scene; use model_converter::math::bounding_box::BoundingBox3; use model_converter::math::vector::Vector3; -use model_converter::parser::parse; +use model_converter::parser::parse_file; use model_converter::renderer::Renderer; use model_converter::controls::{OrbitControls, FirstPersonControls}; use model_converter::camera::Camera; @@ -65,7 +65,7 @@ fn main() { let mut models = vec![]; for input in matches.values_of("input").unwrap() { - match parse(&input) { + match parse_file(&input) { Ok(model) => { if model.vertices.len() > 0 { bbox = bbox.union(&model.bounding_box()); diff --git a/src/scene.rs b/src/scene.rs index 38a9e09..abd1d65 100644 --- a/src/scene.rs +++ b/src/scene.rs @@ -1,6 +1,6 @@ //! This module contains everything related to scenes. -use std::slice::Iter; +use std::slice::{Iter, IterMut}; use std::vec::IntoIter; use std::rc::Rc; use std::cell::RefCell; @@ -33,6 +33,11 @@ impl Scene { pub fn iter(&self) -> Iter>> { self.models.iter() } + + /// Returns a mutable iterator to the model of the scene. + pub fn iter_mut(&mut self) -> IterMut>> { + self.models.iter_mut() + } } impl IntoIterator for Scene {