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/common.rs | |
parent | 33f7772ddddf2a1c2bfefc50ef39f123df8af3e4 (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.rs | 391 |
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)); + } +} |