From 9530d22cf5369ceba369487fff1b85376da64657 Mon Sep 17 00:00:00 2001 From: HampusM Date: Thu, 25 Apr 2024 20:48:39 +0200 Subject: feat(engine): add basic Wavefront obj file parsing --- engine/src/file_format/wavefront/obj.rs | 270 ++++++++++++++++++++++++++++++++ 1 file changed, 270 insertions(+) create mode 100644 engine/src/file_format/wavefront/obj.rs (limited to 'engine/src/file_format/wavefront/obj.rs') diff --git a/engine/src/file_format/wavefront/obj.rs b/engine/src/file_format/wavefront/obj.rs new file mode 100644 index 0000000..d60b41d --- /dev/null +++ b/engine/src/file_format/wavefront/obj.rs @@ -0,0 +1,270 @@ +//! 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}; + +/// The output from parsing the content of a Wavefront `.obj` using [`parse`]. +#[derive(Debug)] +#[non_exhaustive] +pub struct Obj +{ + pub mesh: Mesh, +} + +/// 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::, _>>()?; + + let vertices = faces + .iter() + .flatten() + .map(|face_vertex| { + face_vertex_to_vertex( + face_vertex, + Data { + vertex_positions: &vertex_positions, + texture_positions: &texture_positions, + vertex_normals: &vertex_normals, + }, + ) + }) + .collect::, Error>>()?; + + Ok(Obj { + mesh: Mesh::new( + vertices, + Some( + faces + .iter() + .flatten() + .enumerate() + .map(|(index, _)| { + u32::try_from(index).map_err(|_| Error::FaceIndexTooBig(index)) + }) + .collect::, _>>()?, + ), + ), + }) +} + +struct Data<'a> +{ + vertex_positions: &'a [Vec3], + texture_positions: &'a [Vec2], + vertex_normals: &'a [Vec3], +} + +fn face_vertex_to_vertex(face_vertex: &FaceVertex, data: Data) -> Result +{ + let vertex_pos = *data + .vertex_positions + .get(face_vertex.position as usize - 1) + .ok_or(Error::FaceVertexPositionNotFound { + vertex_pos_index: face_vertex.position, + })?; + + let face_vertex_texture = face_vertex + .texture + .ok_or(Error::NoFaceVertexTextureNotSupported)?; + + let texture_pos = *data + .texture_positions + .get(face_vertex_texture as usize - 1) + .ok_or(Error::FaceTexturePositionNotFound { + texture_pos_index: face_vertex_texture, + })?; + + let face_vertex_normal = face_vertex + .normal + .ok_or(Error::NoFaceVertexNormalNotSupported)?; + + let vertex_normal = *data + .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()) +} + +#[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, + } +} + +#[derive(Debug)] +struct FaceVertex +{ + position: u32, + texture: Option, + normal: Option, +} + +impl From for FaceVertex +{ + fn from(triplet: Triplet) -> Self + { + Self { + position: triplet.0, + texture: triplet.1, + normal: triplet.2, + } + } +} -- cgit v1.2.3-18-g5258