summaryrefslogtreecommitdiff
path: root/engine/src/file_format
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
parentea0e2830db3b78b8d73ac103eede3a9fe1717851 (diff)
feat(engine): add support for Wavefront obj usemtl & mtllib
Diffstat (limited to 'engine/src/file_format')
-rw-r--r--engine/src/file_format/wavefront/common.rs40
-rw-r--r--engine/src/file_format/wavefront/obj.rs121
2 files changed, 156 insertions, 5 deletions
diff --git a/engine/src/file_format/wavefront/common.rs b/engine/src/file_format/wavefront/common.rs
index 0c5ac33..2e27bcb 100644
--- a/engine/src/file_format/wavefront/common.rs
+++ b/engine/src/file_format/wavefront/common.rs
@@ -218,11 +218,19 @@ impl Value
})?));
}
- if value.contains('/') {
+ if value.contains('/')
+ && value
+ .chars()
+ .all(|character| character.is_ascii_digit() || character == '/')
+ {
return Ok(Self::Triplet(Triplet::parse::<KeywordT>(value, line_no)?));
}
- if value.contains(|character: char| character.is_ascii_digit()) {
+ if !value.is_empty()
+ && value
+ .chars()
+ .all(|character: char| character.is_ascii_digit())
+ {
return Ok(Self::Integer(value.parse().map_err(|err| {
ParsingError::InvalidIntegerValue {
err,
@@ -238,6 +246,18 @@ impl Value
Err(ParsingError::UnknownValueType(value.to_string()))
}
+
+ pub fn to_text(
+ &self,
+ argument_index: usize,
+ line_no: usize,
+ ) -> Result<&str, ParsingError>
+ {
+ match self {
+ Value::Text(value) => Ok(value),
+ _ => Err(ParsingError::UnexpectedArgumentType { argument_index, line_no }),
+ }
+ }
}
#[derive(Debug)]
@@ -265,6 +285,22 @@ impl<KeywordT: Keyword> Statement<KeywordT>
}
}
+ pub fn get_text_arg(&self, index: usize, line_no: usize)
+ -> Result<&str, ParsingError>
+ {
+ match self.arguments.get(index) {
+ Some(Value::Text(value)) => Ok(value),
+ Some(_) => Err(ParsingError::UnexpectedArgumentType {
+ argument_index: index,
+ line_no,
+ }),
+ None => Err(ParsingError::MissingArgument {
+ keyword: self.keyword.to_string(),
+ line_no,
+ }),
+ }
+ }
+
pub fn get_triplet_arg(
&self,
index: usize,
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"
+ );
}
}