summaryrefslogtreecommitdiff
path: root/engine/src/file_format/wavefront/obj.rs
diff options
context:
space:
mode:
authorHampusM <hampus@hampusmat.com>2024-04-25 20:48:39 +0200
committerHampusM <hampus@hampusmat.com>2024-05-01 19:18:03 +0200
commit9530d22cf5369ceba369487fff1b85376da64657 (patch)
tree5beeaa594e77a32877336e119035197e8cd5ac9d /engine/src/file_format/wavefront/obj.rs
parent33f7772ddddf2a1c2bfefc50ef39f123df8af3e4 (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.rs270
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,
+ }
+ }
+}