summaryrefslogtreecommitdiff
path: root/engine/src/file_format
diff options
context:
space:
mode:
Diffstat (limited to 'engine/src/file_format')
-rw-r--r--engine/src/file_format/wavefront/common.rs12
-rw-r--r--engine/src/file_format/wavefront/mtl.rs204
-rw-r--r--engine/src/file_format/wavefront/obj.rs734
3 files changed, 542 insertions, 408 deletions
diff --git a/engine/src/file_format/wavefront/common.rs b/engine/src/file_format/wavefront/common.rs
index 8cdfb34..83fd8af 100644
--- a/engine/src/file_format/wavefront/common.rs
+++ b/engine/src/file_format/wavefront/common.rs
@@ -1,5 +1,7 @@
use std::num::{ParseFloatError, ParseIntError};
+use engine_ecs::util::array_vec::ArrayVec;
+
macro_rules! keyword {
(
$(#[$attr: meta])*
@@ -68,9 +70,11 @@ where
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<_>, _>>()?;
+ let mut arguments = ArrayVec::<Value, 16>::default();
+
+ for part in parts {
+ arguments.push(Value::parse::<KeywordT>(part, line_no)?);
+ }
Ok(Some(Statement { keyword, arguments }))
}
@@ -265,7 +269,7 @@ impl Value
pub struct Statement<KeywordT: Keyword>
{
pub keyword: KeywordT,
- pub arguments: Vec<Value>,
+ pub arguments: ArrayVec<Value, 16>,
}
impl<KeywordT: Keyword> Statement<KeywordT>
diff --git a/engine/src/file_format/wavefront/mtl.rs b/engine/src/file_format/wavefront/mtl.rs
index d90dbcf..24387b9 100644
--- a/engine/src/file_format/wavefront/mtl.rs
+++ b/engine/src/file_format/wavefront/mtl.rs
@@ -2,17 +2,15 @@
//!
//! File format documentation: <https://paulbourke.net/dataformats/mtl>
-use std::path::Path;
+use std::path::{Path, PathBuf};
-use crate::color::Color;
+use crate::color::{Color, Rgb};
use crate::file_format::wavefront::common::{
keyword,
parse_statement_line,
ParsingError,
Statement,
};
-use crate::material::{Builder as MaterialBuilder, Material};
-use crate::texture::{Error as TextureError, Texture};
/// Parses the content of a Wavefront `.mtl`.
///
@@ -50,18 +48,41 @@ pub fn parse(obj_content: &str) -> Result<Vec<NamedMaterial>, Error>
}
#[derive(Debug, Clone)]
+#[non_exhaustive]
pub struct NamedMaterial
{
pub name: String,
- pub material: Material,
+ pub ambient: Color<f32>,
+ pub diffuse: Color<f32>,
+ pub specular: Color<f32>,
+ pub ambient_map: Option<TextureMap>,
+ pub diffuse_map: Option<TextureMap>,
+ pub specular_map: Option<TextureMap>,
+ pub shininess: f32,
+}
+
+impl Default for NamedMaterial
+{
+ fn default() -> Self
+ {
+ Self {
+ name: String::new(),
+ ambient: Color::Rgb(Rgb::<f32>::white()),
+ diffuse: Color::Rgb(Rgb::<f32>::white()),
+ specular: Color::Rgb(Rgb::<f32>::white()),
+ ambient_map: None,
+ diffuse_map: None,
+ specular_map: None,
+ shininess: 0.0,
+ }
+ }
}
#[derive(Debug, Clone)]
-pub struct UnfinishedNamedMaterial
+#[non_exhaustive]
+pub struct TextureMap
{
- name: String,
- material_builder: MaterialBuilder,
- ready: bool,
+ pub path: PathBuf,
}
#[derive(Debug, thiserror::Error)]
@@ -70,8 +91,14 @@ pub enum Error
#[error(transparent)]
ParsingError(#[from] ParsingError),
- #[error("Failed to open texture")]
- TextureError(#[from] TextureError),
+ #[error(
+ "A material start statement (newmtl) is expected before statement at line {}",
+ line_no
+ )]
+ ExpectedMaterialStartStmtBeforeStmt
+ {
+ line_no: usize
+ },
#[error(
"Unsupported number of arguments ({arg_count}) to {keyword} at line {line_no}"
@@ -100,59 +127,52 @@ fn statements_to_materials(
{
let mut materials = Vec::<NamedMaterial>::with_capacity(material_cnt);
- let mut curr_material = UnfinishedNamedMaterial {
- name: String::new(),
- material_builder: MaterialBuilder::new(),
- ready: false,
- };
-
for (line_no, statement) in statements {
if statement.keyword == Keyword::Newmtl {
- if curr_material.ready {
- tracing::debug!("Building material");
-
- let material = curr_material.material_builder.clone().build();
-
- materials.push(NamedMaterial { name: curr_material.name, material });
- }
-
let name = statement.get_text_arg(0, line_no)?;
- curr_material.name = name.to_string();
- curr_material.ready = true;
+ materials.push(NamedMaterial {
+ name: name.to_string(),
+ ..Default::default()
+ });
continue;
}
- if !curr_material.ready {
- // Discard statements not belonging to a material
- continue;
+ let Some(curr_material) = materials.last_mut() else {
+ return Err(Error::ExpectedMaterialStartStmtBeforeStmt { line_no });
};
match statement.keyword {
Keyword::Ka => {
let color = get_color_from_statement(&statement, line_no)?;
- tracing::debug!("Adding ambient color");
+ tracing::debug!(
+ "Adding ambient color {color:?} to material {}",
+ curr_material.name
+ );
- curr_material.material_builder =
- curr_material.material_builder.ambient(color);
+ curr_material.ambient = color;
}
Keyword::Kd => {
let color = get_color_from_statement(&statement, line_no)?;
- tracing::debug!("Adding diffuse color");
+ tracing::debug!(
+ "Adding diffuse color {color:?} to material {}",
+ curr_material.name
+ );
- curr_material.material_builder =
- curr_material.material_builder.diffuse(color);
+ curr_material.diffuse = color;
}
Keyword::Ks => {
let color = get_color_from_statement(&statement, line_no)?;
- tracing::debug!("Adding specular color");
+ tracing::debug!(
+ "Adding specular color {color:?} to material {}",
+ curr_material.name
+ );
- curr_material.material_builder =
- curr_material.material_builder.specular(color);
+ curr_material.specular = color;
}
Keyword::MapKa => {
if statement.arguments.len() > 1 {
@@ -165,51 +185,75 @@ fn statements_to_materials(
let texture_file_path = statement.get_text_arg(0, line_no)?;
- let texture = Texture::open(Path::new(texture_file_path))?;
-
- tracing::debug!("Adding ambient map");
+ tracing::debug!(
+ "Adding ambient map {texture_file_path} to material {}",
+ curr_material.name
+ );
- let texture_id = texture.id();
-
- curr_material.material_builder = curr_material
- .material_builder
- .texture(texture)
- .ambient_map(texture_id);
+ curr_material.ambient_map = Some(TextureMap {
+ path: Path::new(texture_file_path).to_path_buf(),
+ });
}
Keyword::MapKd => {
- let texture = get_map_from_texture(&statement, line_no)?;
+ if statement.arguments.len() > 1 {
+ return Err(Error::UnsupportedArgumentCount {
+ keyword: statement.keyword.to_string(),
+ arg_count: statement.arguments.len(),
+ line_no,
+ });
+ }
- tracing::debug!("Adding diffuse map");
+ let texture_file_path = statement.get_text_arg(0, line_no)?;
- let texture_id = texture.id();
+ tracing::debug!(
+ "Adding diffuse map {texture_file_path} to material {}",
+ curr_material.name
+ );
- curr_material.material_builder = curr_material
- .material_builder
- .texture(texture)
- .diffuse_map(texture_id);
+ curr_material.diffuse_map = Some(TextureMap {
+ path: Path::new(texture_file_path).to_path_buf(),
+ });
}
Keyword::MapKs => {
- let texture = get_map_from_texture(&statement, line_no)?;
+ if statement.arguments.len() > 1 {
+ return Err(Error::UnsupportedArgumentCount {
+ keyword: statement.keyword.to_string(),
+ arg_count: statement.arguments.len(),
+ line_no,
+ });
+ }
- tracing::debug!("Adding specular map");
+ let texture_file_path = statement.get_text_arg(0, line_no)?;
- let texture_id = texture.id();
+ tracing::debug!(
+ "Adding specular map {texture_file_path} to material {}",
+ curr_material.name
+ );
- curr_material.material_builder = curr_material
- .material_builder
- .texture(texture)
- .specular_map(texture_id);
+ curr_material.specular_map = Some(TextureMap {
+ path: Path::new(texture_file_path).to_path_buf(),
+ });
}
- Keyword::Newmtl => {}
- }
- }
+ Keyword::Ns => {
+ if statement.arguments.len() != 1 {
+ return Err(Error::UnsupportedArgumentCount {
+ keyword: statement.keyword.to_string(),
+ arg_count: statement.arguments.len(),
+ line_no,
+ });
+ }
- if curr_material.ready {
- tracing::debug!("Building last material");
+ let shininess = statement.get_float_arg(0, line_no)?;
- let material = curr_material.material_builder.build();
+ tracing::debug!(
+ "Adding shininess {shininess} to material {}",
+ curr_material.name
+ );
- materials.push(NamedMaterial { name: curr_material.name, material });
+ curr_material.shininess = shininess;
+ }
+ Keyword::Newmtl => {}
+ }
}
Ok(materials)
@@ -232,25 +276,7 @@ fn get_color_from_statement(
let green = statement.get_float_arg(1, line_no)?;
let blue = statement.get_float_arg(2, line_no)?;
- Ok(Color { red, green, blue })
-}
-
-fn get_map_from_texture(
- statement: &Statement<Keyword>,
- line_no: usize,
-) -> Result<Texture, Error>
-{
- if statement.arguments.len() > 1 {
- return Err(Error::UnsupportedArgumentCount {
- keyword: statement.keyword.to_string(),
- arg_count: statement.arguments.len(),
- line_no,
- });
- }
-
- let texture_file_path = statement.get_text_arg(0, line_no)?;
-
- Ok(Texture::open(Path::new(texture_file_path))?)
+ Ok(Color::Rgb(Rgb { r: red, g: green, b: blue }))
}
keyword! {
@@ -271,5 +297,7 @@ keyword! {
#[keyword(rename = "map_Ks")]
MapKs,
+
+ Ns,
}
}
diff --git a/engine/src/file_format/wavefront/obj.rs b/engine/src/file_format/wavefront/obj.rs
index 6ca11c2..d1a1bf3 100644
--- a/engine/src/file_format/wavefront/obj.rs
+++ b/engine/src/file_format/wavefront/obj.rs
@@ -6,17 +6,24 @@ use std::collections::HashMap;
use std::fs::read_to_string;
use std::path::PathBuf;
+use engine_ecs::util::Either;
+
use crate::file_format::wavefront::common::{
keyword,
parse_statement_line,
+ Keyword as _,
ParsingError,
Statement,
Triplet,
};
-use crate::mesh::Mesh;
-use crate::util::try_option;
+use crate::mesh::vertex_buffer::{
+ NamedVertexAttr,
+ VertexAttrInfo,
+ VertexBuffer as MeshVertexBuffer,
+ VertexLabel,
+};
+use crate::mesh::{Mesh, VertexAttrType};
use crate::vector::{Vec2, Vec3};
-use crate::vertex::{Builder as VertexBuilder, Vertex};
/// Parses the content of a Wavefront `.obj`.
///
@@ -29,34 +36,37 @@ pub fn parse(obj_content: &str) -> Result<Obj, Error>
.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 = get_vertex_positions_from_statements(&statements)?;
- let texture_positions = get_texture_positions_from_statements(&statements)?;
- let vertex_normals = get_vertex_normals_from_statements(&statements)?;
- let material_specs = get_material_specs_from_statements(&statements)?;
- let faces = get_faces_from_statements(&statements, &material_specs)?;
- let mtl_libs = get_mtl_libs_from_statements(&statements)?;
-
- Ok(Obj {
- vertex_positions,
- vertex_normals,
- texture_positions,
- faces,
- mtl_libs,
- })
+ let mut item_counts = ItemCounts::default();
+
+ for (line_index, line) in obj_content.lines().enumerate() {
+ if line.is_empty() || line.starts_with('#') {
+ continue;
+ }
+
+ let Some((keyword, _)) = line.split_once(' ') else {
+ continue;
+ };
+
+ let Ok(keyword) = Keyword::from_str(keyword, line_index + 1) else {
+ continue;
+ };
+
+ item_counts.increment_item_cnt_by_keyword(keyword);
+ }
+
+ let mut obj = Obj::with_capacities_from_item_cnts(item_counts);
+
+ let mut parsing_state = ParsingState::default();
+
+ for (line_no, line) in lines {
+ let Some(statement) = parse_statement_line::<Keyword>(line, line_no)? else {
+ continue;
+ };
+
+ obj.handle_statement(line_no, statement, &mut parsing_state)?;
+ }
+
+ Ok(obj)
}
/// The data of a Wavefront object file.
@@ -68,7 +78,8 @@ pub struct Obj
pub vertex_normals: Vec<Vec3<f32>>,
pub texture_positions: Vec<Vec2<f32>>,
pub faces: Vec<Face>,
- pub mtl_libs: Vec<Vec<PathBuf>>,
+ pub mtl_libs: Vec<PathBuf>,
+ pub unique_used_material_names: Vec<Box<str>>,
}
impl Obj
@@ -81,9 +92,26 @@ impl Obj
/// - A face's texture position cannot be found
/// - A face's vertex normal cannot be found
/// - A face index does not fit in a [`u32`]
- pub fn to_mesh(&self) -> Result<Mesh, Error>
+ pub fn to_mesh(&self, options: ToMeshOptions) -> Result<Mesh, Error>
{
- let mut vertices = Vec::<Vertex>::with_capacity(self.faces.len() * 3);
+ let mut vertex_buf = MeshVertexBuffer::with_capacity(
+ &[
+ VertexAttrInfo {
+ label: VertexLabel::Position,
+ ty: VertexAttrType::Float32Array { length: 3 },
+ },
+ VertexAttrInfo {
+ label: VertexLabel::UvFromTopLeft,
+ ty: VertexAttrType::Float32Array { length: 2 },
+ },
+ VertexAttrInfo {
+ label: VertexLabel::Normal,
+ ty: VertexAttrType::Float32Array { length: 3 },
+ },
+ ],
+ self.faces.len() * 3,
+ );
+
let mut indices = Vec::<u32>::with_capacity(self.faces.len() * 3);
let mut added_face_vertices =
@@ -91,16 +119,86 @@ impl Obj
for face in &self.faces {
for face_vertex in &face.vertices {
- if let Some(index) = added_face_vertices.get(&face_vertex) {
+ if let Some(index) = added_face_vertices.get(face_vertex) {
indices.push(*index);
continue;
}
- vertices.push(face_vertex.to_vertex(self)?);
+ let pos = *self
+ .vertex_positions
+ .get(face_vertex.position as usize - 1)
+ .ok_or(Error::FaceVertexPositionNotFound {
+ vertex_pos_index: face_vertex.position,
+ })?;
+
+ let texture_pos = face_vertex.texture.map_or_else(
+ || {
+ if !self.texture_positions.is_empty() {
+ tracing::warn!(concat!(
+ "Wavefront OBJ has texture coordinates ",
+ "but face vertex does not specify one"
+ ));
+ }
+
+ Ok(Vec2::default())
+ },
+ |face_vertex_texture| {
+ self.texture_positions
+ .get(face_vertex_texture as usize - 1)
+ .ok_or(Error::FaceTexturePositionNotFound {
+ texture_pos_index: face_vertex_texture,
+ })
+ .cloned()
+ },
+ )?;
+
+ let texture_pos = if options.y_flip_uvs {
+ Vec2 { x: texture_pos.x, y: -texture_pos.y }
+ } else {
+ texture_pos
+ };
+
+ let normal = face_vertex.normal.map_or_else(
+ || {
+ if !self.vertex_normals.is_empty() {
+ tracing::warn!(concat!(
+ "Wavefront OBJ has normals ",
+ "but face vertex does not specify one"
+ ));
+ }
+
+ Ok(Vec3::default())
+ },
+ |face_vertex_normal| {
+ self.vertex_normals
+ .get(face_vertex_normal as usize - 1)
+ .ok_or(Error::FaceVertexNormalNotFound {
+ vertex_normal_index: face_vertex_normal,
+ })
+ .cloned()
+ },
+ )?;
+
+ vertex_buf.push((
+ NamedVertexAttr {
+ label: VertexLabel::Position,
+ value: pos.into_array(),
+ },
+ NamedVertexAttr {
+ label: VertexLabel::UvFromTopLeft,
+ value: texture_pos.into_array(),
+ },
+ NamedVertexAttr {
+ label: VertexLabel::Normal,
+ value: normal.into_array(),
+ },
+ ));
+
+ let vertex_index = vertex_buf.len() - 1;
- let vertex_index = u32::try_from(vertices.len() - 1)
- .map_err(|_| Error::FaceIndexTooBig(vertices.len() - 1))?;
+ let vertex_index = u32::try_from(vertex_index)
+ .map_err(|_| Error::FaceIndexTooBig(vertex_index))?;
indices.push(vertex_index);
@@ -108,7 +206,10 @@ impl Obj
}
}
- Ok(Mesh::new(vertices, Some(indices)))
+ Ok(Mesh::builder()
+ .vertices(vertex_buf)
+ .indices(indices)
+ .build())
}
/// Reads and parses the material libraries of this `Obj`.
@@ -124,7 +225,6 @@ impl Obj
{
self.mtl_libs
.iter()
- .flatten()
.map(|mtl_lib| {
Ok(parse_material_lib(&read_to_string(mtl_lib).map_err(
|err| MaterialLibsError::ReadingMaterialLibFailed {
@@ -139,6 +239,238 @@ impl Obj
})
.collect::<Result<Vec<_>, _>>()
}
+
+ fn with_capacities_from_item_cnts(item_counts: ItemCounts) -> Self
+ {
+ Self {
+ vertex_positions: Vec::with_capacity(item_counts.pos),
+ vertex_normals: Vec::with_capacity(item_counts.normal),
+ texture_positions: Vec::with_capacity(item_counts.uv),
+ faces: Vec::with_capacity(item_counts.face),
+ mtl_libs: Vec::with_capacity(item_counts.mtl_lib),
+ unique_used_material_names: Vec::with_capacity(item_counts.material_usage),
+ }
+ }
+
+ fn handle_statement(
+ &mut self,
+ line_no: usize,
+ statement: Statement<Keyword>,
+ parsing_state: &mut ParsingState,
+ ) -> Result<(), Error>
+ {
+ match statement.keyword {
+ Keyword::V => self.handle_position_statement(line_no, statement),
+ Keyword::Vn => self.handle_normal_statement(line_no, statement),
+ Keyword::Vt => self.handle_uv_statement(line_no, statement),
+ Keyword::F => self.handle_face_statement(line_no, statement, parsing_state),
+ Keyword::Mtllib => self.handle_material_lib_statement(line_no, statement),
+ Keyword::Usemtl => {
+ self.handle_material_usage_statement(line_no, statement, parsing_state)
+ }
+ Keyword::O | Keyword::S => Ok(()),
+ }
+ }
+
+ fn handle_position_statement(
+ &mut self,
+ line_no: usize,
+ statement: Statement<Keyword>,
+ ) -> Result<(), Error>
+ {
+ debug_assert_eq!(statement.keyword, Keyword::V);
+
+ if statement.arguments.len() == 4 {
+ return Err(Error::UnsupportedArgumentCount {
+ keyword: statement.keyword.to_string(),
+ arg_count: statement.arguments.len(),
+ line_no,
+ });
+ }
+
+ if statement.arguments.len() > 4 {
+ return Err(Error::InvalidArgumentCount {
+ keyword: statement.keyword.to_string(),
+ arg_count: statement.arguments.len(),
+ line_no,
+ });
+ }
+
+ let x = statement.get_float_arg(0, line_no)?;
+ let y = statement.get_float_arg(1, line_no)?;
+ let z = statement.get_float_arg(2, line_no)?;
+
+ self.vertex_positions.push(Vec3 { x, y, z });
+
+ Ok(())
+ }
+
+ fn handle_uv_statement(
+ &mut self,
+ line_no: usize,
+ statement: Statement<Keyword>,
+ ) -> Result<(), Error>
+ {
+ debug_assert_eq!(statement.keyword, Keyword::Vt);
+
+ if statement.arguments.len() == 3 {
+ return Err(Error::UnsupportedArgumentCount {
+ keyword: statement.keyword.to_string(),
+ arg_count: statement.arguments.len(),
+ line_no,
+ });
+ }
+
+ if statement.arguments.len() > 3 {
+ return Err(Error::InvalidArgumentCount {
+ keyword: statement.keyword.to_string(),
+ arg_count: statement.arguments.len(),
+ line_no,
+ });
+ }
+
+ let u = statement.get_float_arg(0, line_no)?;
+ let v = statement.get_float_arg(1, line_no)?;
+
+ self.texture_positions.push(Vec2 { x: u, y: v });
+
+ Ok(())
+ }
+
+ fn handle_normal_statement(
+ &mut self,
+ line_no: usize,
+ statement: Statement<Keyword>,
+ ) -> Result<(), Error>
+ {
+ debug_assert_eq!(statement.keyword, Keyword::Vn);
+
+ if statement.arguments.len() > 3 {
+ return Err(Error::InvalidArgumentCount {
+ keyword: statement.keyword.to_string(),
+ arg_count: statement.arguments.len(),
+ line_no,
+ });
+ }
+
+ let i = statement.get_float_arg(0, line_no)?;
+ let j = statement.get_float_arg(1, line_no)?;
+ let k = statement.get_float_arg(2, line_no)?;
+
+ self.vertex_normals.push(Vec3 { x: i, y: j, z: k });
+
+ Ok(())
+ }
+
+ fn handle_material_usage_statement(
+ &mut self,
+ line_no: usize,
+ statement: Statement<Keyword>,
+ parsing_state: &mut ParsingState,
+ ) -> Result<(), Error>
+ {
+ debug_assert_eq!(statement.keyword, Keyword::Usemtl);
+
+ if statement.arguments.len() > 1 {
+ return Err(Error::InvalidArgumentCount {
+ keyword: statement.keyword.to_string(),
+ arg_count: statement.arguments.len(),
+ line_no,
+ });
+ }
+
+ let material_name = statement
+ .get_text_arg(0, line_no)?
+ .to_owned()
+ .into_boxed_str();
+
+ if !self.unique_used_material_names.contains(&material_name) {
+ self.unique_used_material_names.push(material_name.clone());
+ }
+
+ parsing_state.current_material_name = Some(material_name);
+
+ Ok(())
+ }
+
+ fn handle_face_statement(
+ &mut self,
+ line_no: usize,
+ statement: Statement<Keyword>,
+ parsing_state: &mut ParsingState,
+ ) -> Result<(), Error>
+ {
+ debug_assert_eq!(statement.keyword, Keyword::F);
+
+ if statement.arguments.len() > 3 {
+ return Err(Error::UnsupportedArgumentCount {
+ keyword: statement.keyword.to_string(),
+ arg_count: statement.arguments.len(),
+ line_no,
+ });
+ }
+
+ let vertex_a = statement.get_triplet_arg(0, line_no)?;
+ let vertex_b = statement.get_triplet_arg(1, line_no)?;
+ let vertex_c = statement.get_triplet_arg(2, line_no)?;
+
+ self.faces.push(Face {
+ vertices: [vertex_a.into(), vertex_b.into(), vertex_c.into()],
+ material_name: parsing_state.current_material_name.clone(),
+ });
+
+ Ok(())
+ }
+
+ fn handle_material_lib_statement(
+ &mut self,
+ line_no: usize,
+ statement: Statement<Keyword>,
+ ) -> Result<(), Error>
+ {
+ debug_assert_eq!(statement.keyword, Keyword::Mtllib);
+
+ for (index, value) in statement.arguments.iter().enumerate() {
+ let mtl_lib = PathBuf::from(value.to_text(index, line_no)?);
+
+ self.mtl_libs.push(mtl_lib);
+ }
+
+ Ok(())
+ }
+}
+
+#[derive(Debug, Clone)]
+#[non_exhaustive]
+pub struct ToMeshOptions
+{
+ /// Whether all UVs should be flipped on the Y axis. This is necessary if the UVs
+ /// have their origin at the bottom left corner of the image
+ ///
+ /// The default is `true`.
+ pub y_flip_uvs: bool,
+}
+
+impl ToMeshOptions
+{
+ /// Whether all UVs should be flipped on the Y axis. This is necessary if the UVs have
+ /// their origin at the bottom left corner of the image
+ ///
+ /// The default is `true`.
+ #[must_use]
+ pub fn y_flip_uvs(mut self, y_flip_uvs: bool) -> Self
+ {
+ self.y_flip_uvs = y_flip_uvs;
+ self
+ }
+}
+
+impl Default for ToMeshOptions
+{
+ fn default() -> Self
+ {
+ Self { y_flip_uvs: true }
+ }
}
#[derive(Debug)]
@@ -146,7 +478,7 @@ impl Obj
pub struct Face
{
pub vertices: [FaceVertex; 3],
- pub material_name: Option<String>,
+ pub material_name: Option<Box<str>>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
@@ -157,51 +489,6 @@ pub struct FaceVertex
pub normal: Option<u32>,
}
-impl FaceVertex
-{
- /// Tries to convert this face vertex into a [`Vertex`].
- ///
- /// # Errors
- /// Returns `Err` if:
- /// - The face's vertex position cannot be found in the given [`Obj`]
- /// - The face's texture position cannot be found in the given [`Obj`]
- /// - The face's vertex normal cannot be found in the given [`Obj`]
- pub fn to_vertex(&self, obj: &Obj) -> Result<Vertex, Error>
- {
- let mut vertex_builder = VertexBuilder::default();
-
- let vertex_pos = *obj.vertex_positions.get(self.position as usize - 1).ok_or(
- Error::FaceVertexPositionNotFound { vertex_pos_index: self.position },
- )?;
-
- vertex_builder = vertex_builder.pos(vertex_pos);
-
- if let Some(face_vertex_texture) = self.texture {
- let texture_pos = obj
- .texture_positions
- .get(face_vertex_texture as usize - 1)
- .ok_or(Error::FaceTexturePositionNotFound {
- texture_pos_index: face_vertex_texture,
- })?;
-
- vertex_builder = vertex_builder.texture_coords(*texture_pos);
- }
-
- if let Some(face_vertex_normal) = self.normal {
- let vertex_normal = *obj
- .vertex_normals
- .get(face_vertex_normal as usize - 1)
- .ok_or(Error::FaceVertexNormalNotFound {
- vertex_normal_index: face_vertex_normal,
- })?;
-
- vertex_builder = vertex_builder.normal(vertex_normal);
- }
-
- Ok(vertex_builder.build())
- }
-}
-
impl From<Triplet> for FaceVertex
{
fn from(triplet: Triplet) -> Self
@@ -281,6 +568,39 @@ pub enum MaterialLibsError<MaterialLibParseError>
},
}
+#[derive(Debug, Default)]
+struct ParsingState
+{
+ current_material_name: Option<Box<str>>,
+}
+
+#[derive(Debug, Default)]
+struct ItemCounts
+{
+ pos: usize,
+ uv: usize,
+ normal: usize,
+ face: usize,
+ mtl_lib: usize,
+ material_usage: usize,
+}
+
+impl ItemCounts
+{
+ fn increment_item_cnt_by_keyword(&mut self, keyword: Keyword)
+ {
+ match keyword {
+ Keyword::V => self.pos += 1,
+ Keyword::Vn => self.normal += 1,
+ Keyword::Vt => self.uv += 1,
+ Keyword::F => self.face += 1,
+ Keyword::Mtllib => self.mtl_lib += 1,
+ Keyword::Usemtl => self.material_usage += 1,
+ Keyword::O | Keyword::S => {}
+ }
+ }
+}
+
keyword! {
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
enum Keyword {
@@ -310,233 +630,6 @@ keyword! {
}
}
-#[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)
-}
-
-enum Either<ValA, ValB>
-{
- A(ValA),
- B(ValB),
-}
-
-impl<ValA, ValB, Item> Iterator for Either<ValA, ValB>
-where
- ValA: Iterator<Item = Item>,
- ValB: Iterator<Item = Item>,
-{
- type Item = Item;
-
- fn next(&mut self) -> Option<Self::Item>
- {
- match self {
- Self::A(iter_a) => iter_a.next(),
- Self::B(iter_b) => iter_b.next(),
- }
- }
-}
-
-fn get_vertex_positions_from_statements(
- statements: &[(usize, Statement<Keyword>)],
-) -> Result<Vec<Vec3<f32>>, Error>
-{
- statements
- .iter()
- .filter_map(|(line_no, statement)| {
- if statement.keyword != Keyword::V {
- return None;
- }
-
- if statement.arguments.len() == 4 {
- return Some(Err(Error::UnsupportedArgumentCount {
- keyword: statement.keyword.to_string(),
- arg_count: statement.arguments.len(),
- line_no: *line_no,
- }));
- }
-
- if statement.arguments.len() > 4 {
- return Some(Err(Error::InvalidArgumentCount {
- keyword: statement.keyword.to_string(),
- arg_count: statement.arguments.len(),
- line_no: *line_no,
- }));
- }
-
- 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<_>, Error>>()
-}
-
-fn get_texture_positions_from_statements(
- statements: &[(usize, Statement<Keyword>)],
-) -> Result<Vec<Vec2<f32>>, Error>
-{
- statements
- .iter()
- .filter_map(|(line_no, statement)| {
- if statement.keyword != Keyword::Vt {
- return None;
- }
-
- if statement.arguments.len() == 3 {
- return Some(Err(Error::UnsupportedArgumentCount {
- keyword: statement.keyword.to_string(),
- arg_count: statement.arguments.len(),
- line_no: *line_no,
- }));
- }
-
- if statement.arguments.len() > 3 {
- return Some(Err(Error::InvalidArgumentCount {
- keyword: statement.keyword.to_string(),
- arg_count: statement.arguments.len(),
- line_no: *line_no,
- }));
- }
-
- let u = try_option!(statement.get_float_arg(0, *line_no));
- let v = try_option!(statement.get_float_arg(1, *line_no));
-
- Some(Ok(Vec2 { x: u, y: v }))
- })
- .collect::<Result<Vec<_>, Error>>()
-}
-
-fn get_vertex_normals_from_statements(
- statements: &[(usize, Statement<Keyword>)],
-) -> Result<Vec<Vec3<f32>>, Error>
-{
- statements
- .iter()
- .filter_map(|(line_no, statement)| {
- if statement.keyword != Keyword::Vn {
- return None;
- }
-
- if statement.arguments.len() > 3 {
- return Some(Err(Error::InvalidArgumentCount {
- keyword: statement.keyword.to_string(),
- arg_count: statement.arguments.len(),
- line_no: *line_no,
- }));
- }
-
- 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<_>, Error>>()
-}
-
-fn get_material_specs_from_statements(
- statements: &[(usize, Statement<Keyword>)],
-) -> Result<Vec<MaterialSpecifier>, Error>
-{
- statements
- .iter()
- .filter_map(|(line_no, statement)| {
- if statement.keyword != Keyword::Usemtl {
- return None;
- }
-
- if statement.arguments.len() > 1 {
- return Some(Err(Error::InvalidArgumentCount {
- keyword: statement.keyword.to_string(),
- arg_count: statement.arguments.len(),
- line_no: *line_no,
- }));
- }
-
- 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<_>, Error>>()
-}
-
-fn get_faces_from_statements(
- statements: &[(usize, Statement<Keyword>)],
- material_specifiers: &[MaterialSpecifier],
-) -> Result<Vec<Face>, Error>
-{
- statements
- .iter()
- .filter_map(|(line_no, statement)| {
- if statement.keyword != Keyword::F {
- return None;
- }
-
- if statement.arguments.len() > 3 {
- return Some(Err(Error::UnsupportedArgumentCount {
- keyword: statement.keyword.to_string(),
- arg_count: statement.arguments.len(),
- line_no: *line_no,
- }));
- }
-
- 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();
-
- 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<Face>, Error>>()
-}
-
-fn get_mtl_libs_from_statements(
- statements: &[(usize, Statement<Keyword>)],
-) -> Result<Vec<Vec<PathBuf>>, Error>
-{
- 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<_>, Error>>()
-}
-
#[cfg(test)]
mod tests
{
@@ -557,17 +650,26 @@ mod tests
assert_eq!(obj.faces.len(), 3);
assert_eq!(
- obj.faces[0].material_name.as_ref().expect("Expected Some"),
+ obj.faces[0]
+ .material_name
+ .as_deref()
+ .expect("Expected Some"),
"dark-green"
);
assert_eq!(
- obj.faces[1].material_name.as_ref().expect("Expected Some"),
+ obj.faces[1]
+ .material_name
+ .as_deref()
+ .expect("Expected Some"),
"dark-green"
);
assert_eq!(
- obj.faces[2].material_name.as_ref().expect("Expected Some"),
+ obj.faces[2]
+ .material_name
+ .as_deref()
+ .expect("Expected Some"),
"light-pink"
);
}