//! OBJ file format parsing. //! //! File format documentation: use crate::color::Color; use crate::file_format::wavefront::common::{ keyword, parse_statement_line, ParsingError, Triplet, }; use crate::mesh::Mesh; use crate::util::try_option; use crate::vector::{Vec2, Vec3}; use crate::vertex::{Builder as VertexBuilder, Vertex}; /// Parses the content of a Wavefront `.obj`. /// /// # Errors /// Will return `Err` if the `.obj` content is formatted incorrectly. pub fn parse(obj_content: &str) -> Result { let lines = obj_content .lines() .enumerate() .map(|(line_index, line)| (line_index + 1, line)); let statements = lines .map(|(line_no, line)| (line_no, parse_statement_line::(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::, _>>()?; let vertex_positions = statements .iter() .filter_map(|(line_no, statement)| { if statement.keyword != Keyword::V { return None; } 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::, _>>()?; let texture_positions = statements .iter() .filter_map(|(line_no, statement)| { if statement.keyword != Keyword::Vt { return None; } let u = try_option!(statement.get_float_arg(0, *line_no)); let v = try_option!(statement.get_float_arg(1, *line_no)); // let w = try_option!(statement.get_float_arg(2, *line_no)); Some(Ok(Vec2 { x: u, y: v })) }) .collect::, _>>()?; let vertex_normals = statements .iter() .filter_map(|(line_no, statement)| { if statement.keyword != Keyword::Vn { return None; } 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::, _>>()?; let faces = statements .iter() .filter_map(|(line_no, statement)| { if statement.keyword != Keyword::F { return None; } 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(); Some(Ok([vertex_a, vertex_b, vertex_c])) }) .collect::, _>>()?; Ok(Obj { vertex_positions, vertex_normals, texture_positions, faces, }) } /// The output from parsing the content of a Wavefront `.obj` using [`parse`]. #[derive(Debug)] #[non_exhaustive] pub struct Obj { pub vertex_positions: Vec>, pub vertex_normals: Vec>, pub texture_positions: Vec>, pub faces: Vec<[FaceVertex; 3]>, } impl Obj { pub fn to_mesh(&self) -> Result { let vertices = self .faces .iter() .flatten() .map(|face_vertex| face_vertex.to_vertex(self)) .collect::, Error>>()?; Ok(Mesh::new( vertices, Some( self.faces .iter() .flatten() .enumerate() .map(|(index, _)| { u32::try_from(index).map_err(|_| Error::FaceIndexTooBig(index)) }) .collect::, _>>()?, ), )) } } #[derive(Debug)] pub struct FaceVertex { pub position: u32, pub texture: Option, pub normal: Option, } impl FaceVertex { /// Tries to convert this face vertex into a [`Vertex`]. pub fn to_vertex(&self, obj: &Obj) -> Result { let vertex_pos = *obj.vertex_positions.get(self.position as usize - 1).ok_or( Error::FaceVertexPositionNotFound { vertex_pos_index: self.position }, )?; let face_vertex_texture = self.texture.ok_or(Error::NoFaceVertexTextureNotSupported)?; let texture_pos = *obj .texture_positions .get(face_vertex_texture as usize - 1) .ok_or(Error::FaceTexturePositionNotFound { texture_pos_index: face_vertex_texture, })?; let face_vertex_normal = self.normal.ok_or(Error::NoFaceVertexNormalNotSupported)?; let vertex_normal = *obj .vertex_normals .get(face_vertex_normal as usize - 1) .ok_or(Error::FaceVertexNormalNotFound { vertex_normal_index: face_vertex_normal, })?; Ok(VertexBuilder::new() .pos(vertex_pos) .color(Color::WHITE_F32) .texture_coords(texture_pos) .normal(vertex_normal) .build() .unwrap()) } } impl From for FaceVertex { fn from(triplet: Triplet) -> Self { Self { position: triplet.0, texture: triplet.1, normal: triplet.2, } } } #[derive(Debug, thiserror::Error)] pub enum Error { #[error(transparent)] ParsingError(#[from] ParsingError), #[error( "Face vertex position with index {vertex_pos_index} (1-based) was not found" )] FaceVertexPositionNotFound { vertex_pos_index: u32 }, #[error( "Face texture position with index {texture_pos_index} (1-based) was not found" )] FaceTexturePositionNotFound { texture_pos_index: u32 }, #[error( "Face vertex normal with index {vertex_normal_index} (1-based) was not found" )] FaceVertexNormalNotFound { vertex_normal_index: u32 }, #[error("Face index {0} is too big to fit into a 32-bit integer")] FaceIndexTooBig(usize), #[error("Face vertices without textures are not yet supported")] NoFaceVertexTextureNotSupported, #[error("Face vertices without normals are not yet supported")] NoFaceVertexNormalNotSupported, } keyword! { #[derive(Debug, PartialEq, Eq, Clone, Copy)] enum Keyword { #[keyword(rename = "v")] V, #[keyword(rename = "vn")] Vn, #[keyword(rename = "vt")] Vt, #[keyword(rename = "o")] O, #[keyword(rename = "s")] S, #[keyword(rename = "f")] F, } }