diff options
author | HampusM <hampus@hampusmat.com> | 2024-04-25 20:48:39 +0200 |
---|---|---|
committer | HampusM <hampus@hampusmat.com> | 2024-05-01 19:18:03 +0200 |
commit | 9530d22cf5369ceba369487fff1b85376da64657 (patch) | |
tree | 5beeaa594e77a32877336e119035197e8cd5ac9d /engine/src/file_format/wavefront/obj.rs | |
parent | 33f7772ddddf2a1c2bfefc50ef39f123df8af3e4 (diff) |
feat(engine): add basic Wavefront obj file parsing
Diffstat (limited to 'engine/src/file_format/wavefront/obj.rs')
-rw-r--r-- | engine/src/file_format/wavefront/obj.rs | 270 |
1 files changed, 270 insertions, 0 deletions
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: <https://paulbourke.net/dataformats/obj> + +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<Obj, Error> +{ + 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::<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 = 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::<Result<Vec<_>, _>>()?; + + 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::<Result<Vec<_>, _>>()?; + + 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::<Result<Vec<_>, _>>()?; + + 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::<Result<Vec<[FaceVertex; 3]>, _>>()?; + + 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::<Result<Vec<_>, Error>>()?; + + Ok(Obj { + mesh: Mesh::new( + vertices, + Some( + faces + .iter() + .flatten() + .enumerate() + .map(|(index, _)| { + u32::try_from(index).map_err(|_| Error::FaceIndexTooBig(index)) + }) + .collect::<Result<Vec<_>, _>>()?, + ), + ), + }) +} + +struct Data<'a> +{ + vertex_positions: &'a [Vec3<f32>], + texture_positions: &'a [Vec2<f32>], + vertex_normals: &'a [Vec3<f32>], +} + +fn face_vertex_to_vertex(face_vertex: &FaceVertex, data: Data) -> Result<Vertex, Error> +{ + 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<u32>, + normal: Option<u32>, +} + +impl From<Triplet> for FaceVertex +{ + fn from(triplet: Triplet) -> Self + { + Self { + position: triplet.0, + texture: triplet.1, + normal: triplet.2, + } + } +} |