summaryrefslogtreecommitdiff
path: root/engine/src/file_format/wavefront/obj.rs
diff options
context:
space:
mode:
authorHampusM <hampus@hampusmat.com>2024-05-07 21:48:12 +0200
committerHampusM <hampus@hampusmat.com>2024-05-07 21:48:12 +0200
commit4d9ce83a8823e457f28f7d8d1377eca2db667d4c (patch)
tree52df772838281214ed652e73c9ea06a418c200df /engine/src/file_format/wavefront/obj.rs
parentea0e2830db3b78b8d73ac103eede3a9fe1717851 (diff)
feat(engine): add support for Wavefront obj usemtl & mtllib
Diffstat (limited to 'engine/src/file_format/wavefront/obj.rs')
-rw-r--r--engine/src/file_format/wavefront/obj.rs121
1 files changed, 118 insertions, 3 deletions
diff --git a/engine/src/file_format/wavefront/obj.rs b/engine/src/file_format/wavefront/obj.rs
index 884229c..7e1629a 100644
--- a/engine/src/file_format/wavefront/obj.rs
+++ b/engine/src/file_format/wavefront/obj.rs
@@ -2,6 +2,8 @@
//!
//! File format documentation: <https://paulbourke.net/dataformats/obj>
+use std::path::PathBuf;
+
use crate::color::Color;
use crate::file_format::wavefront::common::{
keyword,
@@ -84,6 +86,22 @@ pub fn parse(obj_content: &str) -> Result<Obj, Error>
})
.collect::<Result<Vec<_>, _>>()?;
+ let material_specifiers = statements
+ .iter()
+ .filter_map(|(line_no, statement)| {
+ if statement.keyword != Keyword::Usemtl {
+ return None;
+ }
+
+ 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<_>, _>>()?;
+
let faces = statements
.iter()
.filter_map(|(line_no, statement)| {
@@ -95,15 +113,41 @@ pub fn parse(obj_content: &str) -> Result<Obj, Error>
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]))
+ 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<[FaceVertex; 3]>, _>>()?;
+ .collect::<Result<Vec<Face>, _>>()?;
+
+ let mtl_libs = 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<_>, _>>()?;
Ok(Obj {
vertex_positions,
vertex_normals,
texture_positions,
faces,
+ mtl_libs,
})
}
@@ -115,7 +159,8 @@ pub struct Obj
pub vertex_positions: Vec<Vec3<f32>>,
pub vertex_normals: Vec<Vec3<f32>>,
pub texture_positions: Vec<Vec2<f32>>,
- pub faces: Vec<[FaceVertex; 3]>,
+ pub faces: Vec<Face>,
+ pub mtl_libs: Vec<Vec<PathBuf>>,
}
impl Obj
@@ -125,6 +170,7 @@ impl Obj
let vertices = self
.faces
.iter()
+ .map(|face| face.vertices.clone())
.flatten()
.map(|face_vertex| face_vertex.to_vertex(self))
.collect::<Result<Vec<_>, Error>>()?;
@@ -134,6 +180,7 @@ impl Obj
Some(
self.faces
.iter()
+ .map(|face| face.vertices.clone())
.flatten()
.enumerate()
.map(|(index, _)| {
@@ -146,6 +193,14 @@ impl Obj
}
#[derive(Debug)]
+#[non_exhaustive]
+pub struct Face
+{
+ pub vertices: [FaceVertex; 3],
+ pub material_name: Option<String>,
+}
+
+#[derive(Debug, Clone)]
pub struct FaceVertex
{
pub position: u32,
@@ -264,5 +319,65 @@ keyword! {
#[keyword(rename = "f")]
F,
+
+ #[keyword(rename = "mtllib")]
+ Mtllib,
+
+ #[keyword(rename = "usemtl")]
+ Usemtl,
+ }
+}
+
+#[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)
+}
+
+#[cfg(test)]
+mod tests
+{
+ use super::parse;
+
+ #[test]
+ fn parse_containing_usemtl_works()
+ {
+ let obj = parse(concat!(
+ "usemtl dark-green\n",
+ "f 6/8/1 7/5/3 1/3/2\n",
+ "f 1/2/4 4/1/8 3/4/2\n",
+ "usemtl light-pink\n",
+ "f 9/1/7 5/1/2 3/2/7"
+ ))
+ .expect("Expected Ok");
+
+ assert_eq!(obj.faces.len(), 3);
+
+ assert_eq!(
+ obj.faces[0].material_name.as_ref().expect("Expected Some"),
+ "dark-green"
+ );
+
+ assert_eq!(
+ obj.faces[1].material_name.as_ref().expect("Expected Some"),
+ "dark-green"
+ );
+
+ assert_eq!(
+ obj.faces[2].material_name.as_ref().expect("Expected Some"),
+ "light-pink"
+ );
}
}