summaryrefslogtreecommitdiff
path: root/engine/src/file_format/wavefront/common.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/common.rs
parent33f7772ddddf2a1c2bfefc50ef39f123df8af3e4 (diff)
feat(engine): add basic Wavefront obj file parsing
Diffstat (limited to 'engine/src/file_format/wavefront/common.rs')
-rw-r--r--engine/src/file_format/wavefront/common.rs391
1 files changed, 391 insertions, 0 deletions
diff --git a/engine/src/file_format/wavefront/common.rs b/engine/src/file_format/wavefront/common.rs
new file mode 100644
index 0000000..0c5ac33
--- /dev/null
+++ b/engine/src/file_format/wavefront/common.rs
@@ -0,0 +1,391 @@
+use std::num::{ParseFloatError, ParseIntError};
+
+macro_rules! keyword {
+ (
+ $(#[$attr: meta])*
+ $visibility: vis enum $name: ident
+ {
+ $(
+ $(#[keyword(rename = $variant_rename: literal)])?
+ $variant: ident,
+ )*
+ }
+ ) => {
+ $(#[$attr])*
+ $visibility enum $name
+ {
+ $($variant,)*
+ }
+
+ impl $crate::file_format::wavefront::common::Keyword for $name
+ {
+ fn from_str(
+ keyword: &str,
+ line_no: usize
+ ) -> Result<Keyword, $crate::file_format::wavefront::common::ParsingError>
+ {
+ use $crate::file_format::wavefront::common::ParsingError;
+
+ match keyword {
+ $(
+ $crate::util::or!(
+ ($($variant_rename)?)
+ else (stringify!($variant))
+ ) => Ok(Keyword::$variant),)*
+ _ => Err(ParsingError::UnknownKeyword {
+ keyword: keyword.to_string(),
+ line_no,
+ }),
+ }
+ }
+ }
+
+ impl std::fmt::Display for $name
+ {
+ fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result
+ {
+ match self {
+ $(Self::$variant => write!(formatter, stringify!($variant)),)*
+ }
+ }
+ }
+ };
+}
+
+pub(crate) use keyword;
+
+pub fn parse_statement_line<KeywordT>(
+ line: &str,
+ line_no: usize,
+) -> Result<Option<Statement<KeywordT>>, ParsingError>
+where
+ KeywordT: Keyword,
+{
+ if line.is_empty() || line.starts_with('#') {
+ return Ok(None);
+ }
+
+ let mut parts = line.split(' ');
+ let keyword = KeywordT::from_str(parts.next().unwrap(), line_no)?;
+
+ let arguments = parts
+ .map(|part| Value::parse::<KeywordT>(part, line_no))
+ .collect::<Result<Vec<_>, _>>()?;
+
+ Ok(Some(Statement { keyword, arguments }))
+}
+
+#[derive(Debug, thiserror::Error)]
+pub enum ParsingError
+{
+ #[error("Unknown keyword '{keyword}' at line {line_no}")]
+ UnknownKeyword
+ {
+ keyword: String, line_no: usize
+ },
+
+ #[error("Invalid float value '{value}'")]
+ InvalidFloatValue
+ {
+ err: ParseFloatError, value: String
+ },
+
+ #[error("Invalid integer value '{value}' at line {line_no}")]
+ InvalidIntegerValue
+ {
+ err: ParseIntError,
+ value: String,
+ line_no: usize,
+ },
+
+ #[error("Invalid type of value '{0}'")]
+ UnknownValueType(String),
+
+ #[error("Missing triplet value at line {line_no}")]
+ MissingTripletValue
+ {
+ triplet_index: usize,
+ line_no: usize,
+ },
+
+ #[error("Missing argument to {keyword} at {line_no}")]
+ MissingArgument
+ {
+ keyword: String, line_no: usize
+ },
+
+ #[error("Unexpected type of argument {argument_index} at line {line_no}")]
+ UnexpectedArgumentType
+ {
+ argument_index: usize,
+ line_no: usize,
+ },
+}
+
+pub trait Keyword: ToString + Sized
+{
+ fn from_str(keyword: &str, line_no: usize) -> Result<Self, ParsingError>;
+}
+
+type TripletNumber = u32;
+
+#[derive(Debug, Clone)]
+pub struct Triplet(
+ pub TripletNumber,
+ pub Option<TripletNumber>,
+ pub Option<TripletNumber>,
+);
+
+impl Triplet
+{
+ fn parse<KeywordT: Keyword>(
+ triplet: &str,
+ line_no: usize,
+ ) -> Result<Self, ParsingError>
+ {
+ let mut parts = triplet.splitn(3, '/');
+
+ let first = parts
+ .next()
+ .ok_or(ParsingError::MissingTripletValue { triplet_index: 0, line_no })
+ .and_then(|value| {
+ value.parse::<TripletNumber>().map_err(|err| {
+ ParsingError::InvalidIntegerValue {
+ err,
+ value: value.to_string(),
+ line_no,
+ }
+ })
+ })?;
+
+ let second = parts
+ .next()
+ .ok_or(ParsingError::MissingTripletValue { triplet_index: 0, line_no })
+ .and_then(|value| {
+ if value.is_empty() {
+ return Ok(None);
+ }
+
+ Ok(Some(value.parse::<TripletNumber>().map_err(|err| {
+ ParsingError::InvalidIntegerValue {
+ err,
+ value: value.to_string(),
+ line_no,
+ }
+ })?))
+ })?;
+
+ let third = parts
+ .next()
+ .ok_or(ParsingError::MissingTripletValue { triplet_index: 2, line_no })
+ .and_then(|value| {
+ if value.is_empty() {
+ return Ok(None);
+ }
+
+ Ok(Some(value.parse::<TripletNumber>().map_err(|err| {
+ ParsingError::InvalidIntegerValue {
+ err,
+ value: value.to_string(),
+ line_no,
+ }
+ })?))
+ })?;
+
+ Ok(Self(first, second, third))
+ }
+}
+
+#[derive(Debug)]
+pub enum Value
+{
+ Integer(i32),
+ Float(f32),
+ Triplet(Triplet),
+ Text(String),
+}
+
+impl Value
+{
+ fn parse<KeywordT: Keyword>(value: &str, line_no: usize)
+ -> Result<Self, ParsingError>
+ {
+ if value.contains(|character: char| character.is_ascii_digit())
+ && value.contains('.')
+ {
+ return Ok(Self::Float(value.parse().map_err(|err| {
+ ParsingError::InvalidFloatValue { err, value: value.to_string() }
+ })?));
+ }
+
+ if value.contains('/') {
+ return Ok(Self::Triplet(Triplet::parse::<KeywordT>(value, line_no)?));
+ }
+
+ if value.contains(|character: char| character.is_ascii_digit()) {
+ return Ok(Self::Integer(value.parse().map_err(|err| {
+ ParsingError::InvalidIntegerValue {
+ err,
+ value: value.to_string(),
+ line_no,
+ }
+ })?));
+ }
+
+ if value.chars().all(|character| character.is_ascii()) {
+ return Ok(Self::Text(value.to_string()));
+ }
+
+ Err(ParsingError::UnknownValueType(value.to_string()))
+ }
+}
+
+#[derive(Debug)]
+pub struct Statement<KeywordT: Keyword>
+{
+ pub keyword: KeywordT,
+ pub arguments: Vec<Value>,
+}
+
+impl<KeywordT: Keyword> Statement<KeywordT>
+{
+ pub fn get_float_arg(&self, index: usize, line_no: usize)
+ -> Result<f32, ParsingError>
+ {
+ match self.arguments.get(index) {
+ Some(Value::Float(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,
+ line_no: usize,
+ ) -> Result<Triplet, ParsingError>
+ {
+ match self.arguments.get(index) {
+ Some(Value::Triplet(value)) => Ok(value.clone()),
+ Some(_) => Err(ParsingError::UnexpectedArgumentType {
+ argument_index: index,
+ line_no,
+ }),
+ None => Err(ParsingError::MissingArgument {
+ keyword: self.keyword.to_string(),
+ line_no,
+ }),
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests
+{
+ use std::fmt::Display;
+
+ use super::{parse_statement_line, Value};
+ use crate::file_format::wavefront::common::ParsingError;
+
+ #[derive(Debug, PartialEq, Eq)]
+ enum Keyword
+ {
+ V,
+ Vn,
+ F,
+ }
+
+ impl super::Keyword for Keyword
+ {
+ fn from_str(keyword: &str, _line_no: usize) -> Result<Self, ParsingError>
+ {
+ Ok(match keyword {
+ "v" => Self::V,
+ "vn" => Self::Vn,
+ "f" => Self::F,
+ _ => {
+ panic!("Unknown keyword '{keyword}'");
+ }
+ })
+ }
+ }
+
+ impl Display for Keyword
+ {
+ fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result
+ {
+ match self {
+ Self::V => write!(formatter, "v"),
+ Self::Vn => write!(formatter, "vn"),
+ Self::F => write!(formatter, "f"),
+ }
+ }
+ }
+
+ #[test]
+ fn parse_statement_line_works()
+ {
+ assert!(parse_statement_line::<Keyword>("", 1)
+ .is_ok_and(|statement| statement.is_none()));
+
+ let res = parse_statement_line::<Keyword>("v", 3);
+
+ assert!(matches!(res, Ok(Some(statement)) if statement.keyword == Keyword::V));
+ }
+
+ #[test]
+ fn parse_statement_line_with_args_works()
+ {
+ let statement = parse_statement_line::<Keyword>("vn 45 86 11", 3)
+ .expect("Expected Ok")
+ .expect("Expected Some");
+
+ assert_eq!(statement.keyword, Keyword::Vn);
+
+ assert!(matches!(statement.arguments[0], Value::Integer(45)));
+ assert!(matches!(statement.arguments[1], Value::Integer(86)));
+ assert!(matches!(statement.arguments[2], Value::Integer(11)));
+ }
+
+ #[test]
+ fn parse_statement_line_with_triplet_arg()
+ {
+ let statement = parse_statement_line::<Keyword>("f 67/925/23", 1)
+ .expect("Expected Ok")
+ .expect("Expected Some");
+
+ assert_eq!(statement.keyword, Keyword::F);
+
+ let Value::Triplet(triplet) = &statement.arguments[0] else {
+ panic!("Argument is not a triplet");
+ };
+
+ assert_eq!(triplet.0, 67);
+ assert_eq!(triplet.1, Some(925));
+ assert_eq!(triplet.2, Some(23));
+ }
+
+ #[test]
+ fn parse_statement_line_with_two_value_triplet_arg()
+ {
+ let statement = parse_statement_line::<Keyword>("v 102//336", 1)
+ .expect("Expected Ok")
+ .expect("Expected Some");
+
+ assert_eq!(statement.keyword, Keyword::V);
+
+ let Value::Triplet(triplet) = &statement.arguments[0] else {
+ panic!("Argument is not a triplet");
+ };
+
+ assert_eq!(triplet.0, 102);
+ assert!(triplet.1.is_none());
+ assert_eq!(triplet.2, Some(336));
+ }
+}