diff options
Diffstat (limited to 'engine/src/file_format/wavefront')
| -rw-r--r-- | engine/src/file_format/wavefront/common.rs | 12 | ||||
| -rw-r--r-- | engine/src/file_format/wavefront/mtl.rs | 204 | ||||
| -rw-r--r-- | engine/src/file_format/wavefront/obj.rs | 734 |
3 files changed, 542 insertions, 408 deletions
diff --git a/engine/src/file_format/wavefront/common.rs b/engine/src/file_format/wavefront/common.rs index 8cdfb34..83fd8af 100644 --- a/engine/src/file_format/wavefront/common.rs +++ b/engine/src/file_format/wavefront/common.rs @@ -1,5 +1,7 @@ use std::num::{ParseFloatError, ParseIntError}; +use engine_ecs::util::array_vec::ArrayVec; + macro_rules! keyword { ( $(#[$attr: meta])* @@ -68,9 +70,11 @@ where let mut parts = line.split(' '); let keyword = KeywordT::from_str(parts.next().unwrap(), line_no)?; - let arguments = parts - .map(|part| Value::parse::<KeywordT>(part, line_no)) - .collect::<Result<Vec<_>, _>>()?; + let mut arguments = ArrayVec::<Value, 16>::default(); + + for part in parts { + arguments.push(Value::parse::<KeywordT>(part, line_no)?); + } Ok(Some(Statement { keyword, arguments })) } @@ -265,7 +269,7 @@ impl Value pub struct Statement<KeywordT: Keyword> { pub keyword: KeywordT, - pub arguments: Vec<Value>, + pub arguments: ArrayVec<Value, 16>, } impl<KeywordT: Keyword> Statement<KeywordT> diff --git a/engine/src/file_format/wavefront/mtl.rs b/engine/src/file_format/wavefront/mtl.rs index d90dbcf..24387b9 100644 --- a/engine/src/file_format/wavefront/mtl.rs +++ b/engine/src/file_format/wavefront/mtl.rs @@ -2,17 +2,15 @@ //! //! File format documentation: <https://paulbourke.net/dataformats/mtl> -use std::path::Path; +use std::path::{Path, PathBuf}; -use crate::color::Color; +use crate::color::{Color, Rgb}; use crate::file_format::wavefront::common::{ keyword, parse_statement_line, ParsingError, Statement, }; -use crate::material::{Builder as MaterialBuilder, Material}; -use crate::texture::{Error as TextureError, Texture}; /// Parses the content of a Wavefront `.mtl`. /// @@ -50,18 +48,41 @@ pub fn parse(obj_content: &str) -> Result<Vec<NamedMaterial>, Error> } #[derive(Debug, Clone)] +#[non_exhaustive] pub struct NamedMaterial { pub name: String, - pub material: Material, + pub ambient: Color<f32>, + pub diffuse: Color<f32>, + pub specular: Color<f32>, + pub ambient_map: Option<TextureMap>, + pub diffuse_map: Option<TextureMap>, + pub specular_map: Option<TextureMap>, + pub shininess: f32, +} + +impl Default for NamedMaterial +{ + fn default() -> Self + { + Self { + name: String::new(), + ambient: Color::Rgb(Rgb::<f32>::white()), + diffuse: Color::Rgb(Rgb::<f32>::white()), + specular: Color::Rgb(Rgb::<f32>::white()), + ambient_map: None, + diffuse_map: None, + specular_map: None, + shininess: 0.0, + } + } } #[derive(Debug, Clone)] -pub struct UnfinishedNamedMaterial +#[non_exhaustive] +pub struct TextureMap { - name: String, - material_builder: MaterialBuilder, - ready: bool, + pub path: PathBuf, } #[derive(Debug, thiserror::Error)] @@ -70,8 +91,14 @@ pub enum Error #[error(transparent)] ParsingError(#[from] ParsingError), - #[error("Failed to open texture")] - TextureError(#[from] TextureError), + #[error( + "A material start statement (newmtl) is expected before statement at line {}", + line_no + )] + ExpectedMaterialStartStmtBeforeStmt + { + line_no: usize + }, #[error( "Unsupported number of arguments ({arg_count}) to {keyword} at line {line_no}" @@ -100,59 +127,52 @@ fn statements_to_materials( { let mut materials = Vec::<NamedMaterial>::with_capacity(material_cnt); - let mut curr_material = UnfinishedNamedMaterial { - name: String::new(), - material_builder: MaterialBuilder::new(), - ready: false, - }; - for (line_no, statement) in statements { if statement.keyword == Keyword::Newmtl { - if curr_material.ready { - tracing::debug!("Building material"); - - let material = curr_material.material_builder.clone().build(); - - materials.push(NamedMaterial { name: curr_material.name, material }); - } - let name = statement.get_text_arg(0, line_no)?; - curr_material.name = name.to_string(); - curr_material.ready = true; + materials.push(NamedMaterial { + name: name.to_string(), + ..Default::default() + }); continue; } - if !curr_material.ready { - // Discard statements not belonging to a material - continue; + let Some(curr_material) = materials.last_mut() else { + return Err(Error::ExpectedMaterialStartStmtBeforeStmt { line_no }); }; match statement.keyword { Keyword::Ka => { let color = get_color_from_statement(&statement, line_no)?; - tracing::debug!("Adding ambient color"); + tracing::debug!( + "Adding ambient color {color:?} to material {}", + curr_material.name + ); - curr_material.material_builder = - curr_material.material_builder.ambient(color); + curr_material.ambient = color; } Keyword::Kd => { let color = get_color_from_statement(&statement, line_no)?; - tracing::debug!("Adding diffuse color"); + tracing::debug!( + "Adding diffuse color {color:?} to material {}", + curr_material.name + ); - curr_material.material_builder = - curr_material.material_builder.diffuse(color); + curr_material.diffuse = color; } Keyword::Ks => { let color = get_color_from_statement(&statement, line_no)?; - tracing::debug!("Adding specular color"); + tracing::debug!( + "Adding specular color {color:?} to material {}", + curr_material.name + ); - curr_material.material_builder = - curr_material.material_builder.specular(color); + curr_material.specular = color; } Keyword::MapKa => { if statement.arguments.len() > 1 { @@ -165,51 +185,75 @@ fn statements_to_materials( let texture_file_path = statement.get_text_arg(0, line_no)?; - let texture = Texture::open(Path::new(texture_file_path))?; - - tracing::debug!("Adding ambient map"); + tracing::debug!( + "Adding ambient map {texture_file_path} to material {}", + curr_material.name + ); - let texture_id = texture.id(); - - curr_material.material_builder = curr_material - .material_builder - .texture(texture) - .ambient_map(texture_id); + curr_material.ambient_map = Some(TextureMap { + path: Path::new(texture_file_path).to_path_buf(), + }); } Keyword::MapKd => { - let texture = get_map_from_texture(&statement, line_no)?; + if statement.arguments.len() > 1 { + return Err(Error::UnsupportedArgumentCount { + keyword: statement.keyword.to_string(), + arg_count: statement.arguments.len(), + line_no, + }); + } - tracing::debug!("Adding diffuse map"); + let texture_file_path = statement.get_text_arg(0, line_no)?; - let texture_id = texture.id(); + tracing::debug!( + "Adding diffuse map {texture_file_path} to material {}", + curr_material.name + ); - curr_material.material_builder = curr_material - .material_builder - .texture(texture) - .diffuse_map(texture_id); + curr_material.diffuse_map = Some(TextureMap { + path: Path::new(texture_file_path).to_path_buf(), + }); } Keyword::MapKs => { - let texture = get_map_from_texture(&statement, line_no)?; + if statement.arguments.len() > 1 { + return Err(Error::UnsupportedArgumentCount { + keyword: statement.keyword.to_string(), + arg_count: statement.arguments.len(), + line_no, + }); + } - tracing::debug!("Adding specular map"); + let texture_file_path = statement.get_text_arg(0, line_no)?; - let texture_id = texture.id(); + tracing::debug!( + "Adding specular map {texture_file_path} to material {}", + curr_material.name + ); - curr_material.material_builder = curr_material - .material_builder - .texture(texture) - .specular_map(texture_id); + curr_material.specular_map = Some(TextureMap { + path: Path::new(texture_file_path).to_path_buf(), + }); } - Keyword::Newmtl => {} - } - } + Keyword::Ns => { + if statement.arguments.len() != 1 { + return Err(Error::UnsupportedArgumentCount { + keyword: statement.keyword.to_string(), + arg_count: statement.arguments.len(), + line_no, + }); + } - if curr_material.ready { - tracing::debug!("Building last material"); + let shininess = statement.get_float_arg(0, line_no)?; - let material = curr_material.material_builder.build(); + tracing::debug!( + "Adding shininess {shininess} to material {}", + curr_material.name + ); - materials.push(NamedMaterial { name: curr_material.name, material }); + curr_material.shininess = shininess; + } + Keyword::Newmtl => {} + } } Ok(materials) @@ -232,25 +276,7 @@ fn get_color_from_statement( let green = statement.get_float_arg(1, line_no)?; let blue = statement.get_float_arg(2, line_no)?; - Ok(Color { red, green, blue }) -} - -fn get_map_from_texture( - statement: &Statement<Keyword>, - line_no: usize, -) -> Result<Texture, Error> -{ - if statement.arguments.len() > 1 { - return Err(Error::UnsupportedArgumentCount { - keyword: statement.keyword.to_string(), - arg_count: statement.arguments.len(), - line_no, - }); - } - - let texture_file_path = statement.get_text_arg(0, line_no)?; - - Ok(Texture::open(Path::new(texture_file_path))?) + Ok(Color::Rgb(Rgb { r: red, g: green, b: blue })) } keyword! { @@ -271,5 +297,7 @@ keyword! { #[keyword(rename = "map_Ks")] MapKs, + + Ns, } } diff --git a/engine/src/file_format/wavefront/obj.rs b/engine/src/file_format/wavefront/obj.rs index 6ca11c2..d1a1bf3 100644 --- a/engine/src/file_format/wavefront/obj.rs +++ b/engine/src/file_format/wavefront/obj.rs @@ -6,17 +6,24 @@ use std::collections::HashMap; use std::fs::read_to_string; use std::path::PathBuf; +use engine_ecs::util::Either; + use crate::file_format::wavefront::common::{ keyword, parse_statement_line, + Keyword as _, ParsingError, Statement, Triplet, }; -use crate::mesh::Mesh; -use crate::util::try_option; +use crate::mesh::vertex_buffer::{ + NamedVertexAttr, + VertexAttrInfo, + VertexBuffer as MeshVertexBuffer, + VertexLabel, +}; +use crate::mesh::{Mesh, VertexAttrType}; use crate::vector::{Vec2, Vec3}; -use crate::vertex::{Builder as VertexBuilder, Vertex}; /// Parses the content of a Wavefront `.obj`. /// @@ -29,34 +36,37 @@ pub fn parse(obj_content: &str) -> Result<Obj, Error> .enumerate() .map(|(line_index, line)| (line_index + 1, line)); - let statements = lines - .map(|(line_no, line)| (line_no, parse_statement_line::<Keyword>(line, line_no))) - .filter_map(|(line_no, result)| { - let opt_statement = match result { - Ok(opt_statement) => opt_statement, - Err(err) => { - return Some(Err(err)); - } - }; - - Some(Ok((line_no, opt_statement?))) - }) - .collect::<Result<Vec<_>, _>>()?; - - let vertex_positions = get_vertex_positions_from_statements(&statements)?; - let texture_positions = get_texture_positions_from_statements(&statements)?; - let vertex_normals = get_vertex_normals_from_statements(&statements)?; - let material_specs = get_material_specs_from_statements(&statements)?; - let faces = get_faces_from_statements(&statements, &material_specs)?; - let mtl_libs = get_mtl_libs_from_statements(&statements)?; - - Ok(Obj { - vertex_positions, - vertex_normals, - texture_positions, - faces, - mtl_libs, - }) + let mut item_counts = ItemCounts::default(); + + for (line_index, line) in obj_content.lines().enumerate() { + if line.is_empty() || line.starts_with('#') { + continue; + } + + let Some((keyword, _)) = line.split_once(' ') else { + continue; + }; + + let Ok(keyword) = Keyword::from_str(keyword, line_index + 1) else { + continue; + }; + + item_counts.increment_item_cnt_by_keyword(keyword); + } + + let mut obj = Obj::with_capacities_from_item_cnts(item_counts); + + let mut parsing_state = ParsingState::default(); + + for (line_no, line) in lines { + let Some(statement) = parse_statement_line::<Keyword>(line, line_no)? else { + continue; + }; + + obj.handle_statement(line_no, statement, &mut parsing_state)?; + } + + Ok(obj) } /// The data of a Wavefront object file. @@ -68,7 +78,8 @@ pub struct Obj pub vertex_normals: Vec<Vec3<f32>>, pub texture_positions: Vec<Vec2<f32>>, pub faces: Vec<Face>, - pub mtl_libs: Vec<Vec<PathBuf>>, + pub mtl_libs: Vec<PathBuf>, + pub unique_used_material_names: Vec<Box<str>>, } impl Obj @@ -81,9 +92,26 @@ impl Obj /// - A face's texture position cannot be found /// - A face's vertex normal cannot be found /// - A face index does not fit in a [`u32`] - pub fn to_mesh(&self) -> Result<Mesh, Error> + pub fn to_mesh(&self, options: ToMeshOptions) -> Result<Mesh, Error> { - let mut vertices = Vec::<Vertex>::with_capacity(self.faces.len() * 3); + let mut vertex_buf = MeshVertexBuffer::with_capacity( + &[ + VertexAttrInfo { + label: VertexLabel::Position, + ty: VertexAttrType::Float32Array { length: 3 }, + }, + VertexAttrInfo { + label: VertexLabel::UvFromTopLeft, + ty: VertexAttrType::Float32Array { length: 2 }, + }, + VertexAttrInfo { + label: VertexLabel::Normal, + ty: VertexAttrType::Float32Array { length: 3 }, + }, + ], + self.faces.len() * 3, + ); + let mut indices = Vec::<u32>::with_capacity(self.faces.len() * 3); let mut added_face_vertices = @@ -91,16 +119,86 @@ impl Obj for face in &self.faces { for face_vertex in &face.vertices { - if let Some(index) = added_face_vertices.get(&face_vertex) { + if let Some(index) = added_face_vertices.get(face_vertex) { indices.push(*index); continue; } - vertices.push(face_vertex.to_vertex(self)?); + let pos = *self + .vertex_positions + .get(face_vertex.position as usize - 1) + .ok_or(Error::FaceVertexPositionNotFound { + vertex_pos_index: face_vertex.position, + })?; + + let texture_pos = face_vertex.texture.map_or_else( + || { + if !self.texture_positions.is_empty() { + tracing::warn!(concat!( + "Wavefront OBJ has texture coordinates ", + "but face vertex does not specify one" + )); + } + + Ok(Vec2::default()) + }, + |face_vertex_texture| { + self.texture_positions + .get(face_vertex_texture as usize - 1) + .ok_or(Error::FaceTexturePositionNotFound { + texture_pos_index: face_vertex_texture, + }) + .cloned() + }, + )?; + + let texture_pos = if options.y_flip_uvs { + Vec2 { x: texture_pos.x, y: -texture_pos.y } + } else { + texture_pos + }; + + let normal = face_vertex.normal.map_or_else( + || { + if !self.vertex_normals.is_empty() { + tracing::warn!(concat!( + "Wavefront OBJ has normals ", + "but face vertex does not specify one" + )); + } + + Ok(Vec3::default()) + }, + |face_vertex_normal| { + self.vertex_normals + .get(face_vertex_normal as usize - 1) + .ok_or(Error::FaceVertexNormalNotFound { + vertex_normal_index: face_vertex_normal, + }) + .cloned() + }, + )?; + + vertex_buf.push(( + NamedVertexAttr { + label: VertexLabel::Position, + value: pos.into_array(), + }, + NamedVertexAttr { + label: VertexLabel::UvFromTopLeft, + value: texture_pos.into_array(), + }, + NamedVertexAttr { + label: VertexLabel::Normal, + value: normal.into_array(), + }, + )); + + let vertex_index = vertex_buf.len() - 1; - let vertex_index = u32::try_from(vertices.len() - 1) - .map_err(|_| Error::FaceIndexTooBig(vertices.len() - 1))?; + let vertex_index = u32::try_from(vertex_index) + .map_err(|_| Error::FaceIndexTooBig(vertex_index))?; indices.push(vertex_index); @@ -108,7 +206,10 @@ impl Obj } } - Ok(Mesh::new(vertices, Some(indices))) + Ok(Mesh::builder() + .vertices(vertex_buf) + .indices(indices) + .build()) } /// Reads and parses the material libraries of this `Obj`. @@ -124,7 +225,6 @@ impl Obj { self.mtl_libs .iter() - .flatten() .map(|mtl_lib| { Ok(parse_material_lib(&read_to_string(mtl_lib).map_err( |err| MaterialLibsError::ReadingMaterialLibFailed { @@ -139,6 +239,238 @@ impl Obj }) .collect::<Result<Vec<_>, _>>() } + + fn with_capacities_from_item_cnts(item_counts: ItemCounts) -> Self + { + Self { + vertex_positions: Vec::with_capacity(item_counts.pos), + vertex_normals: Vec::with_capacity(item_counts.normal), + texture_positions: Vec::with_capacity(item_counts.uv), + faces: Vec::with_capacity(item_counts.face), + mtl_libs: Vec::with_capacity(item_counts.mtl_lib), + unique_used_material_names: Vec::with_capacity(item_counts.material_usage), + } + } + + fn handle_statement( + &mut self, + line_no: usize, + statement: Statement<Keyword>, + parsing_state: &mut ParsingState, + ) -> Result<(), Error> + { + match statement.keyword { + Keyword::V => self.handle_position_statement(line_no, statement), + Keyword::Vn => self.handle_normal_statement(line_no, statement), + Keyword::Vt => self.handle_uv_statement(line_no, statement), + Keyword::F => self.handle_face_statement(line_no, statement, parsing_state), + Keyword::Mtllib => self.handle_material_lib_statement(line_no, statement), + Keyword::Usemtl => { + self.handle_material_usage_statement(line_no, statement, parsing_state) + } + Keyword::O | Keyword::S => Ok(()), + } + } + + fn handle_position_statement( + &mut self, + line_no: usize, + statement: Statement<Keyword>, + ) -> Result<(), Error> + { + debug_assert_eq!(statement.keyword, Keyword::V); + + if statement.arguments.len() == 4 { + return Err(Error::UnsupportedArgumentCount { + keyword: statement.keyword.to_string(), + arg_count: statement.arguments.len(), + line_no, + }); + } + + if statement.arguments.len() > 4 { + return Err(Error::InvalidArgumentCount { + keyword: statement.keyword.to_string(), + arg_count: statement.arguments.len(), + line_no, + }); + } + + let x = statement.get_float_arg(0, line_no)?; + let y = statement.get_float_arg(1, line_no)?; + let z = statement.get_float_arg(2, line_no)?; + + self.vertex_positions.push(Vec3 { x, y, z }); + + Ok(()) + } + + fn handle_uv_statement( + &mut self, + line_no: usize, + statement: Statement<Keyword>, + ) -> Result<(), Error> + { + debug_assert_eq!(statement.keyword, Keyword::Vt); + + if statement.arguments.len() == 3 { + return Err(Error::UnsupportedArgumentCount { + keyword: statement.keyword.to_string(), + arg_count: statement.arguments.len(), + line_no, + }); + } + + if statement.arguments.len() > 3 { + return Err(Error::InvalidArgumentCount { + keyword: statement.keyword.to_string(), + arg_count: statement.arguments.len(), + line_no, + }); + } + + let u = statement.get_float_arg(0, line_no)?; + let v = statement.get_float_arg(1, line_no)?; + + self.texture_positions.push(Vec2 { x: u, y: v }); + + Ok(()) + } + + fn handle_normal_statement( + &mut self, + line_no: usize, + statement: Statement<Keyword>, + ) -> Result<(), Error> + { + debug_assert_eq!(statement.keyword, Keyword::Vn); + + if statement.arguments.len() > 3 { + return Err(Error::InvalidArgumentCount { + keyword: statement.keyword.to_string(), + arg_count: statement.arguments.len(), + line_no, + }); + } + + let i = statement.get_float_arg(0, line_no)?; + let j = statement.get_float_arg(1, line_no)?; + let k = statement.get_float_arg(2, line_no)?; + + self.vertex_normals.push(Vec3 { x: i, y: j, z: k }); + + Ok(()) + } + + fn handle_material_usage_statement( + &mut self, + line_no: usize, + statement: Statement<Keyword>, + parsing_state: &mut ParsingState, + ) -> Result<(), Error> + { + debug_assert_eq!(statement.keyword, Keyword::Usemtl); + + if statement.arguments.len() > 1 { + return Err(Error::InvalidArgumentCount { + keyword: statement.keyword.to_string(), + arg_count: statement.arguments.len(), + line_no, + }); + } + + let material_name = statement + .get_text_arg(0, line_no)? + .to_owned() + .into_boxed_str(); + + if !self.unique_used_material_names.contains(&material_name) { + self.unique_used_material_names.push(material_name.clone()); + } + + parsing_state.current_material_name = Some(material_name); + + Ok(()) + } + + fn handle_face_statement( + &mut self, + line_no: usize, + statement: Statement<Keyword>, + parsing_state: &mut ParsingState, + ) -> Result<(), Error> + { + debug_assert_eq!(statement.keyword, Keyword::F); + + if statement.arguments.len() > 3 { + return Err(Error::UnsupportedArgumentCount { + keyword: statement.keyword.to_string(), + arg_count: statement.arguments.len(), + line_no, + }); + } + + let vertex_a = statement.get_triplet_arg(0, line_no)?; + let vertex_b = statement.get_triplet_arg(1, line_no)?; + let vertex_c = statement.get_triplet_arg(2, line_no)?; + + self.faces.push(Face { + vertices: [vertex_a.into(), vertex_b.into(), vertex_c.into()], + material_name: parsing_state.current_material_name.clone(), + }); + + Ok(()) + } + + fn handle_material_lib_statement( + &mut self, + line_no: usize, + statement: Statement<Keyword>, + ) -> Result<(), Error> + { + debug_assert_eq!(statement.keyword, Keyword::Mtllib); + + for (index, value) in statement.arguments.iter().enumerate() { + let mtl_lib = PathBuf::from(value.to_text(index, line_no)?); + + self.mtl_libs.push(mtl_lib); + } + + Ok(()) + } +} + +#[derive(Debug, Clone)] +#[non_exhaustive] +pub struct ToMeshOptions +{ + /// Whether all UVs should be flipped on the Y axis. This is necessary if the UVs + /// have their origin at the bottom left corner of the image + /// + /// The default is `true`. + pub y_flip_uvs: bool, +} + +impl ToMeshOptions +{ + /// Whether all UVs should be flipped on the Y axis. This is necessary if the UVs have + /// their origin at the bottom left corner of the image + /// + /// The default is `true`. + #[must_use] + pub fn y_flip_uvs(mut self, y_flip_uvs: bool) -> Self + { + self.y_flip_uvs = y_flip_uvs; + self + } +} + +impl Default for ToMeshOptions +{ + fn default() -> Self + { + Self { y_flip_uvs: true } + } } #[derive(Debug)] @@ -146,7 +478,7 @@ impl Obj pub struct Face { pub vertices: [FaceVertex; 3], - pub material_name: Option<String>, + pub material_name: Option<Box<str>>, } #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -157,51 +489,6 @@ pub struct FaceVertex pub normal: Option<u32>, } -impl FaceVertex -{ - /// Tries to convert this face vertex into a [`Vertex`]. - /// - /// # Errors - /// Returns `Err` if: - /// - The face's vertex position cannot be found in the given [`Obj`] - /// - The face's texture position cannot be found in the given [`Obj`] - /// - The face's vertex normal cannot be found in the given [`Obj`] - pub fn to_vertex(&self, obj: &Obj) -> Result<Vertex, Error> - { - let mut vertex_builder = VertexBuilder::default(); - - let vertex_pos = *obj.vertex_positions.get(self.position as usize - 1).ok_or( - Error::FaceVertexPositionNotFound { vertex_pos_index: self.position }, - )?; - - vertex_builder = vertex_builder.pos(vertex_pos); - - if let Some(face_vertex_texture) = self.texture { - let texture_pos = obj - .texture_positions - .get(face_vertex_texture as usize - 1) - .ok_or(Error::FaceTexturePositionNotFound { - texture_pos_index: face_vertex_texture, - })?; - - vertex_builder = vertex_builder.texture_coords(*texture_pos); - } - - if let Some(face_vertex_normal) = self.normal { - let vertex_normal = *obj - .vertex_normals - .get(face_vertex_normal as usize - 1) - .ok_or(Error::FaceVertexNormalNotFound { - vertex_normal_index: face_vertex_normal, - })?; - - vertex_builder = vertex_builder.normal(vertex_normal); - } - - Ok(vertex_builder.build()) - } -} - impl From<Triplet> for FaceVertex { fn from(triplet: Triplet) -> Self @@ -281,6 +568,39 @@ pub enum MaterialLibsError<MaterialLibParseError> }, } +#[derive(Debug, Default)] +struct ParsingState +{ + current_material_name: Option<Box<str>>, +} + +#[derive(Debug, Default)] +struct ItemCounts +{ + pos: usize, + uv: usize, + normal: usize, + face: usize, + mtl_lib: usize, + material_usage: usize, +} + +impl ItemCounts +{ + fn increment_item_cnt_by_keyword(&mut self, keyword: Keyword) + { + match keyword { + Keyword::V => self.pos += 1, + Keyword::Vn => self.normal += 1, + Keyword::Vt => self.uv += 1, + Keyword::F => self.face += 1, + Keyword::Mtllib => self.mtl_lib += 1, + Keyword::Usemtl => self.material_usage += 1, + Keyword::O | Keyword::S => {} + } + } +} + keyword! { #[derive(Debug, PartialEq, Eq, Clone, Copy)] enum Keyword { @@ -310,233 +630,6 @@ keyword! { } } -#[derive(Debug)] -struct MaterialSpecifier -{ - material_name: String, - line_no: usize, -} - -fn find_material_specifier_for_line_no( - material_specifiers: &[MaterialSpecifier], - line_no: usize, -) -> Option<&MaterialSpecifier> -{ - material_specifiers - .iter() - .rev() - .find(|material_specifier| material_specifier.line_no < line_no) -} - -enum Either<ValA, ValB> -{ - A(ValA), - B(ValB), -} - -impl<ValA, ValB, Item> Iterator for Either<ValA, ValB> -where - ValA: Iterator<Item = Item>, - ValB: Iterator<Item = Item>, -{ - type Item = Item; - - fn next(&mut self) -> Option<Self::Item> - { - match self { - Self::A(iter_a) => iter_a.next(), - Self::B(iter_b) => iter_b.next(), - } - } -} - -fn get_vertex_positions_from_statements( - statements: &[(usize, Statement<Keyword>)], -) -> Result<Vec<Vec3<f32>>, Error> -{ - statements - .iter() - .filter_map(|(line_no, statement)| { - if statement.keyword != Keyword::V { - return None; - } - - if statement.arguments.len() == 4 { - return Some(Err(Error::UnsupportedArgumentCount { - keyword: statement.keyword.to_string(), - arg_count: statement.arguments.len(), - line_no: *line_no, - })); - } - - if statement.arguments.len() > 4 { - return Some(Err(Error::InvalidArgumentCount { - keyword: statement.keyword.to_string(), - arg_count: statement.arguments.len(), - line_no: *line_no, - })); - } - - let x = try_option!(statement.get_float_arg(0, *line_no)); - let y = try_option!(statement.get_float_arg(1, *line_no)); - let z = try_option!(statement.get_float_arg(2, *line_no)); - - Some(Ok(Vec3 { x, y, z })) - }) - .collect::<Result<Vec<_>, Error>>() -} - -fn get_texture_positions_from_statements( - statements: &[(usize, Statement<Keyword>)], -) -> Result<Vec<Vec2<f32>>, Error> -{ - statements - .iter() - .filter_map(|(line_no, statement)| { - if statement.keyword != Keyword::Vt { - return None; - } - - if statement.arguments.len() == 3 { - return Some(Err(Error::UnsupportedArgumentCount { - keyword: statement.keyword.to_string(), - arg_count: statement.arguments.len(), - line_no: *line_no, - })); - } - - if statement.arguments.len() > 3 { - return Some(Err(Error::InvalidArgumentCount { - keyword: statement.keyword.to_string(), - arg_count: statement.arguments.len(), - line_no: *line_no, - })); - } - - let u = try_option!(statement.get_float_arg(0, *line_no)); - let v = try_option!(statement.get_float_arg(1, *line_no)); - - Some(Ok(Vec2 { x: u, y: v })) - }) - .collect::<Result<Vec<_>, Error>>() -} - -fn get_vertex_normals_from_statements( - statements: &[(usize, Statement<Keyword>)], -) -> Result<Vec<Vec3<f32>>, Error> -{ - statements - .iter() - .filter_map(|(line_no, statement)| { - if statement.keyword != Keyword::Vn { - return None; - } - - if statement.arguments.len() > 3 { - return Some(Err(Error::InvalidArgumentCount { - keyword: statement.keyword.to_string(), - arg_count: statement.arguments.len(), - line_no: *line_no, - })); - } - - let i = try_option!(statement.get_float_arg(0, *line_no)); - let j = try_option!(statement.get_float_arg(1, *line_no)); - let k = try_option!(statement.get_float_arg(2, *line_no)); - - Some(Ok(Vec3 { x: i, y: j, z: k })) - }) - .collect::<Result<Vec<_>, Error>>() -} - -fn get_material_specs_from_statements( - statements: &[(usize, Statement<Keyword>)], -) -> Result<Vec<MaterialSpecifier>, Error> -{ - statements - .iter() - .filter_map(|(line_no, statement)| { - if statement.keyword != Keyword::Usemtl { - return None; - } - - if statement.arguments.len() > 1 { - return Some(Err(Error::InvalidArgumentCount { - keyword: statement.keyword.to_string(), - arg_count: statement.arguments.len(), - line_no: *line_no, - })); - } - - let material_name = try_option!(statement.get_text_arg(0, *line_no)); - - Some(Ok(MaterialSpecifier { - material_name: material_name.to_string(), - line_no: *line_no, - })) - }) - .collect::<Result<Vec<_>, Error>>() -} - -fn get_faces_from_statements( - statements: &[(usize, Statement<Keyword>)], - material_specifiers: &[MaterialSpecifier], -) -> Result<Vec<Face>, Error> -{ - statements - .iter() - .filter_map(|(line_no, statement)| { - if statement.keyword != Keyword::F { - return None; - } - - if statement.arguments.len() > 3 { - return Some(Err(Error::UnsupportedArgumentCount { - keyword: statement.keyword.to_string(), - arg_count: statement.arguments.len(), - line_no: *line_no, - })); - } - - let vertex_a = try_option!(statement.get_triplet_arg(0, *line_no)).into(); - let vertex_b = try_option!(statement.get_triplet_arg(1, *line_no)).into(); - let vertex_c = try_option!(statement.get_triplet_arg(2, *line_no)).into(); - - let material_name = - find_material_specifier_for_line_no(material_specifiers, *line_no) - .map(|material_specifier| material_specifier.material_name.clone()); - - Some(Ok(Face { - vertices: [vertex_a, vertex_b, vertex_c], - material_name, - })) - }) - .collect::<Result<Vec<Face>, Error>>() -} - -fn get_mtl_libs_from_statements( - statements: &[(usize, Statement<Keyword>)], -) -> Result<Vec<Vec<PathBuf>>, Error> -{ - statements - .iter() - .filter_map(|(line_no, statement)| { - if statement.keyword != Keyword::Mtllib { - return None; - } - - let mtl_lib_paths = try_option!(statement - .arguments - .iter() - .enumerate() - .map(|(index, value)| Ok(PathBuf::from(value.to_text(index, *line_no)?))) - .collect::<Result<Vec<_>, ParsingError>>()); - - Some(Ok(mtl_lib_paths)) - }) - .collect::<Result<Vec<_>, Error>>() -} - #[cfg(test)] mod tests { @@ -557,17 +650,26 @@ mod tests assert_eq!(obj.faces.len(), 3); assert_eq!( - obj.faces[0].material_name.as_ref().expect("Expected Some"), + obj.faces[0] + .material_name + .as_deref() + .expect("Expected Some"), "dark-green" ); assert_eq!( - obj.faces[1].material_name.as_ref().expect("Expected Some"), + obj.faces[1] + .material_name + .as_deref() + .expect("Expected Some"), "dark-green" ); assert_eq!( - obj.faces[2].material_name.as_ref().expect("Expected Some"), + obj.faces[2] + .material_name + .as_deref() + .expect("Expected Some"), "light-pink" ); } |
