tobj/
lib.rs

1//! # Tiny OBJ Loader
2//!
3//! A tiny OBJ loader, inspired by Syoyo's excellent [`tinyobjloader`](https://github.com/syoyo/tinyobjloader).
4//! Aims to be a simple and lightweight option for loading `OBJ` files.
5//!
6//! Just returns two `Vec`s containing loaded models and materials.
7//!
8//! ## Triangulation
9//!
10//! Meshes can be triangulated on the fly or left as-is.
11//!
12//! Only polygons that are trivially convertible to triangle fans are supported.
13//! Arbitrary polygons may not behave as expected. The best solution would be to
14//! convert your mesh to solely consist of triangles in your modeling software.
15//!
16//! ## Optional – Normals & Texture Coordinates
17//!
18//! It is assumed that all meshes will at least have positions, but normals and
19//! texture coordinates are optional.
20//!
21//! If no normals or texture coordinates are found then the corresponding
22//! [`Vec`](Mesh::normals)s for the [`Mesh`] will be empty.
23//!
24//! ## Flat Data
25//!
26//! Values are stored packed as [`f32`]s (or [`f64`]s with the use_f64 feature)
27//! in flat `Vec`s.
28//!
29//! For example, the `positions` member of a `Mesh` will contain `[x, y, z, x,
30//! y, z, ...]` which you can then use however you like.
31//!
32//! ## Indices
33//!
34//! Indices are also loaded and may re-use vertices already existing in the
35//! mesh, this data is stored in the [`indices`](Mesh::indices) member.
36//!
37//! When a `Mesh` contains *per vertex per face* normals or texture coordinates,
38//! positions can be duplicated to be *per vertex per face* too via the
39//! [`single_index`](LoadOptions::single_index) flag. This potentially changes
40//! the topology (faces may become disconnected even though their vertices still
41//! share a position in space).
42//!
43//! By default separate indices for normals and texture coordinates are created.
44//! This also guarantees that the topology of the `Mesh` does *not* change when
45//! either of the latter are specified *per vertex per face*.
46//!
47//! ## Materials
48//!
49//! Standard `MTL` attributes are supported too. Any unrecognized parameters
50//! will be stored in a `HashMap` containing the key-value pairs of the
51//! unrecognized parameter and its value.
52//!
53//! ## Example
54//!
55//! In this simple example we load the classic Cornell Box model that only
56//! defines positions and print out its attributes. This example is a slightly
57//! trimmed down version of `print_model_info` and `print_material_info`
58//! combined together, see them for a version that also prints out normals and
59//! texture coordinates if the model has them.
60//!
61//! The [`LoadOptions`] used are typical for the case when the mesh is going to
62//! be sent to a realtime rendering context (game engine, GPU etc.).
63//!
64//! ```
65//! use tobj;
66//!
67//! let cornell_box = tobj::load_obj("obj/cornell_box.obj", &tobj::GPU_LOAD_OPTIONS);
68//! assert!(cornell_box.is_ok());
69//!
70//! let (models, materials) = cornell_box.expect("Failed to load OBJ file");
71//!
72//! // Materials might report a separate loading error if the MTL file wasn't found.
73//! // If you don't need the materials, you can generate a default here and use that
74//! // instead.
75//! let materials = materials.expect("Failed to load MTL file");
76//!
77//! println!("# of models: {}", models.len());
78//! println!("# of materials: {}", materials.len());
79//!
80//! for (i, m) in models.iter().enumerate() {
81//!     let mesh = &m.mesh;
82//!
83//!     println!("model[{}].name = \'{}\'", i, m.name);
84//!     println!("model[{}].mesh.material_id = {:?}", i, mesh.material_id);
85//!
86//!     println!(
87//!         "Size of model[{}].face_arities: {}",
88//!         i,
89//!         mesh.face_arities.len()
90//!     );
91//!
92//!     let mut next_face = 0;
93//!     for f in 0..mesh.face_arities.len() {
94//!         let end = next_face + mesh.face_arities[f] as usize;
95//!         let face_indices: Vec<_> = mesh.indices[next_face..end].iter().collect();
96//!         println!("    face[{}] = {:?}", f, face_indices);
97//!         next_face = end;
98//!     }
99//!
100//!     // Normals and texture coordinates are also loaded, but not printed in this example
101//!     println!("model[{}].vertices: {}", i, mesh.positions.len() / 3);
102//!
103//!     assert!(mesh.positions.len() % 3 == 0);
104//!     for v in 0..mesh.positions.len() / 3 {
105//!         println!(
106//!             "    v[{}] = ({}, {}, {})",
107//!             v,
108//!             mesh.positions[3 * v],
109//!             mesh.positions[3 * v + 1],
110//!             mesh.positions[3 * v + 2]
111//!         );
112//!     }
113//! }
114//!
115//! for (i, m) in materials.iter().enumerate() {
116//!     println!("material[{}].name = \'{}\'", i, m.name);
117//!     if let Some(ambient) = m.ambient {
118//!         println!(
119//!             "    material.Ka = ({}, {}, {})",
120//!             ambient[0], ambient[1], ambient[2]
121//!         );
122//!     }
123//!     if let Some(diffuse) = m.diffuse {
124//!         println!(
125//!             "    material.Kd = ({}, {}, {})",
126//!             diffuse[0], diffuse[1], diffuse[2]
127//!         );
128//!     }
129//!     if let Some(specular) = m.specular {
130//!         println!(
131//!             "    material.Ks = ({}, {}, {})",
132//!             specular[0], specular[1], specular[2]
133//!         );
134//!     }
135//!     if let Some(shininess) = m.shininess {
136//!         println!("    material.Ns = {}", shininess);
137//!     }
138//!     if let Some(dissolve) = m.dissolve {
139//!         println!("    material.d = {}", dissolve);
140//!     }
141//!     if let Some(ambient_texture) = &m.ambient_texture {
142//!         println!("    material.map_Ka = {}", ambient_texture);
143//!     }
144//!     if let Some(diffuse_texture) = &m.diffuse_texture {
145//!         println!("    material.map_Kd = {}", diffuse_texture);
146//!     }
147//!     if let Some(specular_texture) = &m.specular_texture {
148//!         println!("    material.map_Ks = {}", specular_texture);
149//!     }
150//!     if let Some(shininess_texture) = &m.shininess_texture {
151//!         println!("    material.map_Ns = {}", shininess_texture);
152//!     }
153//!     if let Some(normal_texture) = &m.normal_texture {
154//!         println!("    material.map_Bump = {}", normal_texture);
155//!     }
156//!     if let Some(dissolve_texture) = &m.dissolve_texture {
157//!         println!("    material.map_d = {}", dissolve_texture);
158//!     }
159//!
160//!     for (k, v) in &m.unknown_param {
161//!         println!("    material.{} = {}", k, v);
162//!     }
163//! }
164//! ```
165//!
166//! ## Rendering Examples
167//!
168//! For an example of integration with [glium](https://github.com/tomaka/glium)
169//! to make a simple OBJ viewer, check out [`tobj viewer`](https://github.com/Twinklebear/tobj_viewer).
170//! Some more sample images can be found in [this gallery](http://imgur.com/a/xsg6v).
171//!
172//! The Rungholt model shown below is reasonably large (6.7M triangles, 12.3M
173//! vertices) and is loaded in ~7.47s using a peak of ~1.1GB of memory on a
174//! Windows 10 machine with an i7-4790k and 16GB of 1600Mhz DDR3 RAM with tobj
175//! 0.1.1 on rustc 1.6.0. The model can be found on [Morgan McGuire's](http://graphics.cs.williams.edu/data/meshes.xml)
176//! meshes page and was originally built by kescha. Future work will focus on
177//! improving performance and memory usage.
178//!
179//! <img src="http://i.imgur.com/wImyNG4.png" alt="Rungholt"
180//!     style="display:block; max-width:100%; height:auto">
181//!
182//! For an example of integration within a ray tracer, check out tray\_rust's
183//! [mesh module](https://github.com/Twinklebear/tray_rust/blob/master/src/geometry/mesh.rs).
184//! The Stanford Buddha and Dragon from the
185//! [Stanford 3D Scanning Repository](http://graphics.stanford.edu/data/3Dscanrep/)
186//! both load quite quickly. The Rust logo model was made by [Nylithius on BlenderArtists](http://blenderartists.org/forum/showthread.php?362836-Rust-language-3D-logo).
187//! The materials used are from the [MERL BRDF Database](http://www.merl.com/brdf/).
188//!
189//! <img src="http://i.imgur.com/E1ylrZW.png" alt="Rust logo with friends"
190//!     style="display:block; max-width:100%; height:auto">
191//!
192//! ## Features
193//!
194//! * [`ahash`](https://crates.io/crates/ahash) – On by default. Use [`AHashMap`](https://docs.rs/ahash/latest/ahash/struct.AHashMap.html)
195//!   for hashing when reading files and merging vertices. To disable and use
196//!   the slower [`HashMap`](std::collections::HashMap) instead, unset default
197//! features in `Cargo.toml`:
198//!
199//!   ```toml
200//!   [dependencies.tobj]
201//!   default-features = false
202//!   ```
203//!
204//! * [`merging`](LoadOptions::merge_identical_points) – Adds support for
205//!   merging identical vertex positions on disconnected faces during import.
206//!
207//!   **Warning:** this feature uses *const generics* and thus requires at
208//!   least a `beta` toolchain to build.
209//!
210//! * [`reordering`](LoadOptions::reorder_data) – Adds support for reordering
211//!   the normal- and texture coordinate indices.
212//!
213//! * [`async`](load_obj_buf_async) – Adds support for async loading of obj
214//!   files from a buffer, with an async material loader. Useful in environments
215//!   that do not support blocking IO (e.g. WebAssembly).
216//!
217//! * [`futures`](futures) - Adds support for async loading of objs and materials
218//!   using [futures](https://crates.io/crates/futures) [AsyncRead](futures_lite::AsyncRead)
219//!   traits.
220//!
221//! * [`tokio`](tokio) - Adds support for async loading of objs and materials
222//!   using [tokio](https://crates.io/crates/tokio) [AsyncRead](::tokio::io::AsyncRead)
223//!   traits.
224//!
225//! * ['use_f64'] - Uses double-precision (f64) instead of single-precision
226//!   (f32) floating point types
227#![cfg_attr(feature = "merging", allow(incomplete_features))]
228#![cfg_attr(feature = "merging", feature(generic_const_exprs))]
229
230#[cfg(test)]
231mod tests;
232
233use std::{
234    error::Error,
235    fmt,
236    fs::File,
237    io::{prelude::*, BufReader},
238    path::{Path, PathBuf},
239    str::{FromStr, SplitWhitespace},
240};
241
242#[cfg(feature = "use_f64")]
243type Float = f64;
244
245#[cfg(not(feature = "use_f64"))]
246type Float = f32;
247
248#[cfg(feature = "async")]
249use std::future::Future;
250
251#[cfg(feature = "merging")]
252use std::mem::size_of;
253
254#[cfg(feature = "ahash")]
255type HashMap<K, V> = ahash::AHashMap<K, V>;
256
257#[cfg(not(feature = "ahash"))]
258type HashMap<K, V> = std::collections::HashMap<K, V>;
259
260/// Typical [`LoadOptions`] for using meshes in a GPU/relatime context.
261///
262/// Faces are *triangulated*, a *single index* is generated and *degenerate
263/// faces* (points & lines) are *discarded*.
264pub const GPU_LOAD_OPTIONS: LoadOptions = LoadOptions {
265    #[cfg(feature = "merging")]
266    merge_identical_points: false,
267    #[cfg(feature = "reordering")]
268    reorder_data: false,
269    single_index: true,
270    triangulate: true,
271    ignore_points: true,
272    ignore_lines: true,
273};
274
275/// Typical [`LoadOptions`] for using meshes with an offline rendeder.
276///
277/// Faces are *kept as they are* (e.g. n-gons) and *normal and texture
278/// coordinate data is reordered* so only a single index is needed.
279/// Topology remains unchanged except for *degenerate faces* (points & lines)
280/// which are *discarded*.
281pub const OFFLINE_RENDERING_LOAD_OPTIONS: LoadOptions = LoadOptions {
282    #[cfg(feature = "merging")]
283    merge_identical_points: true,
284    #[cfg(feature = "reordering")]
285    reorder_data: true,
286    single_index: false,
287    triangulate: false,
288    ignore_points: true,
289    ignore_lines: true,
290};
291
292/// A mesh made up of triangles loaded from some `OBJ` file.
293///
294/// It is assumed that all meshes will at least have positions, but normals and
295/// texture coordinates are optional. If no normals or texture coordinates where
296/// found then the corresponding `Vec`s in the `Mesh` will be empty. Values are
297/// stored packed as [`f32`]s  (or [`f64`]s with the use_f64 feature) in  flat
298/// `Vec`s.
299///
300/// For examples the `positions` member of a loaded mesh will contain `[x, y, z,
301/// x, y, z, ...]` which you can then use however you like. Indices are also
302/// loaded and may re-use vertices already existing in the mesh. This data is
303/// stored in the `indices` member.
304///
305/// # Example:
306/// Load the Cornell box and get the attributes of the first vertex. It's
307/// assumed all meshes will have positions (required), but normals and texture
308/// coordinates are optional, in which case the corresponding `Vec` will be
309/// empty.
310///
311/// ```
312/// let cornell_box = tobj::load_obj("obj/cornell_box.obj", &tobj::GPU_LOAD_OPTIONS);
313/// assert!(cornell_box.is_ok());
314///
315/// let (models, materials) = cornell_box.unwrap();
316///
317/// let mesh = &models[0].mesh;
318/// let i = mesh.indices[0] as usize;
319///
320/// // pos = [x, y, z]
321/// let pos = [
322///     mesh.positions[i * 3],
323///     mesh.positions[i * 3 + 1],
324///     mesh.positions[i * 3 + 2],
325/// ];
326///
327/// if !mesh.normals.is_empty() {
328///     // normal = [x, y, z]
329///     let normal = [
330///         mesh.normals[i * 3],
331///         mesh.normals[i * 3 + 1],
332///         mesh.normals[i * 3 + 2],
333///     ];
334/// }
335///
336/// if !mesh.texcoords.is_empty() {
337///     // texcoord = [u, v];
338///     let texcoord = [mesh.texcoords[i * 2], mesh.texcoords[i * 2 + 1]];
339/// }
340/// ```
341#[derive(Debug, Clone, Default)]
342pub struct Mesh {
343    /// Flattened 3 component floating point vectors, storing positions of
344    /// vertices in the mesh.
345    pub positions: Vec<Float>,
346    /// Flattened 3 component floating point vectors, storing the color
347    /// associated with the vertices in the mesh.
348    ///
349    /// Most meshes do not have vertex colors. If no vertex colors are specified
350    /// this will be empty.
351    pub vertex_color: Vec<Float>,
352    /// Flattened 3 component floating point vectors, storing normals of
353    /// vertices in the mesh.
354    ///
355    /// Not all meshes have normals. If no normals are specified this will
356    /// be empty.
357    pub normals: Vec<Float>,
358    /// Flattened 2 component floating point vectors, storing texture
359    /// coordinates of vertices in the mesh.
360    ///
361    /// Not all meshes have texture coordinates. If no texture coordinates are
362    /// specified this will be empty.
363    pub texcoords: Vec<Float>,
364    /// Indices for vertices of each face. If loaded with
365    /// [`triangulate`](LoadOptions::triangulate) set to `true` each face in the
366    /// mesh is a triangle.
367    ///
368    /// Otherwise [`face_arities`](Mesh::face_arities) indicates how many
369    /// indices are used by each face.
370    ///
371    /// When [`single_index`](LoadOptions::single_index) is set to `true`,
372    /// these indices are for *all* of the data in the mesh. Positions,
373    /// normals and texture coordinaes.
374    /// Otherwise normals and texture coordinates have *their own* indices,
375    /// each.
376    pub indices: Vec<u32>,
377    /// The number of vertices (arity) of each face. *Empty* if loaded with
378    /// `triangulate` set to `true` or if the mesh constists *only* of
379    /// triangles.
380    ///
381    /// The offset for the starting index of a face can be found by iterating
382    /// through the `face_arities` until reaching the desired face, accumulating
383    /// the number of vertices used so far.
384    pub face_arities: Vec<u32>,
385    /// The indices for vertex colors. Only present when the
386    /// [`merging`](LoadOptions::merge_identical_points) feature is enabled, and
387    /// empty unless the corresponding load option is set to `true`.
388    #[cfg(feature = "merging")]
389    pub vertex_color_indices: Vec<u32>,
390    /// The indices for texture coordinates. Can be omitted by setting
391    /// `single_index` to `true`.
392    pub texcoord_indices: Vec<u32>,
393    /// The indices for normals. Can be omitted by setting `single_index` to
394    /// `true`.
395    pub normal_indices: Vec<u32>,
396    /// Optional material id associated with this mesh. The material id indexes
397    /// into the Vec of Materials loaded from the associated `MTL` file
398    pub material_id: Option<usize>,
399}
400
401/// Options for processing the mesh during loading.
402///
403/// Passed to [`load_obj()`], [`load_obj_buf()`] and [`load_obj_buf_async()`].
404///
405/// By default, all of these are `false`. With those settings, the data you get
406/// represents the original data in the input file/buffer as closely as
407/// possible.
408///
409/// Use the [init struct pattern](https://xaeroxe.github.io/init-struct-pattern/) to set individual options:
410/// ```ignore
411/// LoadOptions {
412///     single_index: true,
413///     ..Default::default()
414/// }
415/// ```
416///
417/// There are convenience `const`s for the most common cases:
418///
419/// * [`GPU_LOAD_OPTIONS`] – if you display meshes on the GPU/in realtime.
420///
421/// * [`OFFLINE_RENDERING_LOAD_OPTIONS`] – if you're rendering meshes with e.g.
422///   an offline path tracer or the like.
423#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
424#[derive(Debug, Default, Clone, Copy)]
425pub struct LoadOptions {
426    /// Merge identical positions.
427    ///
428    /// This is usually what you want if you intend to use the mesh in an
429    /// *offline rendering* context or to do further processing with
430    /// *topological operators*.
431    ///
432    /// * This flag is *mutually exclusive* with
433    ///   [`single_index`](LoadOptions::single_index) and will lead to a
434    ///   [`InvalidLoadOptionConfig`](LoadError::InvalidLoadOptionConfig) error
435    ///   if both are set to `true`.
436    ///
437    /// * If adjacent faces share vertices that have separate `indices` but the
438    ///   same position in 3D they will be merged into a single vertex and the
439    ///   resp. `indices` changed.
440    ///
441    /// * Topolgy may change as a result (faces may become *connected* in the
442    ///   index).
443    #[cfg(feature = "merging")]
444    pub merge_identical_points: bool,
445    /// Normal & texture coordinates will be reordered to allow omitting their
446    /// indices.
447    ///
448    /// * This flag is *mutually exclusive* with
449    ///   [`single_index`](LoadOptions::single_index) and will lead to an
450    ///   [`InvalidLoadOptionConfig`](LoadError::InvalidLoadOptionConfig) error
451    ///   if both are set to `true`.
452    ///
453    /// * The resulting [`Mesh`]'s `normal_indices` and/or `texcoord_indices`
454    ///   will be empty.
455    ///
456    /// * *Per-vertex* normals and/or texture_coordinates will be reordered to
457    ///   match the `Mesh`'s `indices`.
458    ///
459    /// * *Per-vertex-per-face*  normals and/or texture coordinates indices will
460    ///   be `[0, 1, 2, ..., n]`. I.e.:
461    ///
462    ///   ```ignore
463    ///   // If normals where specified per-vertex-per-face:
464    ///   assert!(mesh.indices.len() == mesh.normals.len() / 3);
465    ///
466    ///   for index in 0..mesh.indices.len() {
467    ///       println!("Normal n is {}, {}, {}",
468    ///           mesh.normals[index * 3 + 0],
469    ///           mesh.normals[index * 3 + 1],
470    ///           mesh.normals[index * 3 + 2]
471    ///       );
472    ///   }
473    ///   ```
474    #[cfg(feature = "reordering")]
475    pub reorder_data: bool,
476    /// Create a single index.
477    ///
478    /// This is usually what you want if you are loading the mesh to display in
479    /// a *realtime* (*GPU*) context.
480    ///
481    /// * This flag is *mutually exclusive* with both
482    ///   [`merge_identical_points`](LoadOptions::merge_identical_points) and
483    ///   [`reorder_data`](LoadOptions::reorder_data) resp. and will lead to a
484    ///   [`InvalidLoadOptionConfig`](LoadError::InvalidLoadOptionConfig) error
485    ///   if both it and either of the two other are set to `true`.
486    ///
487    /// * Vertices may get duplicated to match the granularity
488    ///   (*per-vertex-per-face*) of normals and/or texture coordinates.
489    ///
490    /// * Topolgy may change as a result (faces may become *disconnected* in the
491    ///   index).
492    ///
493    /// * The resulting [`Mesh`]'s [`normal_indices`](Mesh::normal_indices) and
494    ///   [`texcoord_indices`](Mesh::texcoord_indices) will be empty.
495    pub single_index: bool,
496    /// Triangulate all faces.
497    ///
498    /// * Points (one point) and lines (two points) are blown up to zero area
499    ///   triangles via point duplication. Except if `ignore_points` or
500    ///   `ignore_lines` is/are set to `true`, resp.
501    ///
502    /// * The resulting `Mesh`'s [`face_arities`](Mesh::face_arities) will be
503    ///   empty as all faces are guranteed to have arity `3`.
504    ///
505    /// * Only polygons that are trivially convertible to triangle fans are
506    ///   supported. Arbitrary polygons may not behave as expected. The best
507    ///   solution would be to convert your mesh to solely consist of triangles
508    ///   in your modeling software.
509    pub triangulate: bool,
510    /// Ignore faces containing only a single vertex (points).
511    ///
512    /// This is usually what you want if you do *not* intend to make special use
513    /// of the point data (e.g. as particles etc.).
514    ///
515    /// Polygon meshes that contain faces with one vertex only usually do so
516    /// because of bad topology.
517    pub ignore_points: bool,
518    /// Ignore faces containing only two vertices (lines).
519    ///
520    /// This is usually what you want if you do *not* intend to make special use
521    /// of the line data (e.g. as wires/ropes etc.).
522    ///
523    /// Polygon meshes that contains faces with two vertices only usually do so
524    /// because of bad topology.
525    pub ignore_lines: bool,
526}
527
528impl LoadOptions {
529    /// Checks if the given `LoadOptions` do not contain mutually exclusive flag
530    /// settings.
531    ///
532    /// This is called by [`load_obj()`]/[`load_obj_buf()`] in any case. This
533    /// method is only exposed for scenarios where you want to do this check
534    /// yourself.
535    pub fn is_valid(&self) -> bool {
536        // A = single_index, B = merge_identical_points, C = reorder_data
537        // (A ∧ ¬B) ∨ (A ∧ ¬C) -> A ∧ ¬(B ∨ C)
538        #[allow(unused_mut)]
539        let mut other_flags = false;
540
541        #[cfg(feature = "merging")]
542        {
543            other_flags = other_flags || self.merge_identical_points;
544        }
545        #[cfg(feature = "reordering")]
546        {
547            other_flags = other_flags || self.reorder_data;
548        }
549
550        (self.single_index != other_flags) || (!self.single_index && !other_flags)
551    }
552}
553
554/// A named model within the file.
555///
556/// Associates some mesh with a name that was specified with an `o` or `g`
557/// keyword in the `OBJ` file.
558#[derive(Clone, Debug)]
559pub struct Model {
560    /// [`Mesh`] used by the model containing its geometry.
561    pub mesh: Mesh,
562    /// Name assigned to this `Mesh`.
563    pub name: String,
564}
565
566impl Model {
567    /// Create a new model, associating a name with a [`Mesh`].
568    pub fn new(mesh: Mesh, name: String) -> Model {
569        Model { mesh, name }
570    }
571}
572
573/// A material that may be referenced by one or more [`Mesh`]es.
574///
575/// Standard `MTL` attributes are supported. Any unrecognized parameters will be
576/// stored as key-value pairs in the `unknown_param`
577/// [`HashMap`](std::collections::HashMap), which maps the unknown parameter to
578/// the value set for it.
579///
580/// No path is pre-pended to the texture file names specified in the `MTL` file.
581#[derive(Clone, Debug, Default)]
582pub struct Material {
583    /// Material name as specified in the `MTL` file.
584    pub name: String,
585    /// Ambient color of the material.
586    pub ambient: Option<[Float; 3]>,
587    /// Diffuse color of the material.
588    pub diffuse: Option<[Float; 3]>,
589    /// Specular color of the material.
590    pub specular: Option<[Float; 3]>,
591    /// Material shininess attribute. Also called `glossiness`.
592    pub shininess: Option<Float>,
593    /// Dissolve attribute is the alpha term for the material. Referred to as
594    /// dissolve since that's what the `MTL` file format docs refer to it as.
595    pub dissolve: Option<Float>,
596    /// Optical density also known as index of refraction. Called
597    /// `optical_density` in the `MTL` specc. Takes on a value between 0.001
598    /// and 10.0. 1.0 means light does not bend as it passes through
599    /// the object.
600    pub optical_density: Option<Float>,
601    /// Name of the ambient texture file for the material.
602    pub ambient_texture: Option<String>,
603    /// Name of the diffuse texture file for the material.
604    pub diffuse_texture: Option<String>,
605    /// Name of the specular texture file for the material.
606    pub specular_texture: Option<String>,
607    /// Name of the normal map texture file for the material.
608    pub normal_texture: Option<String>,
609    /// Name of the shininess map texture file for the material.
610    pub shininess_texture: Option<String>,
611    /// Name of the alpha/opacity map texture file for the material.
612    ///
613    /// Referred to as `dissolve` to match the `MTL` file format specification.
614    pub dissolve_texture: Option<String>,
615    /// The illumnination model to use for this material. The different
616    /// illumination models are specified in the [`MTL` spec](http://paulbourke.net/dataformats/mtl/).
617    pub illumination_model: Option<u8>,
618    /// Key value pairs of any unrecognized parameters encountered while parsing
619    /// the material.
620    pub unknown_param: HashMap<String, String>,
621}
622
623/// Possible errors that may occur while loading `OBJ` and `MTL` files.
624#[derive(Debug, Clone, Copy, PartialEq)]
625pub enum LoadError {
626    OpenFileFailed,
627    ReadError,
628    UnrecognizedCharacter,
629    PositionParseError,
630    NormalParseError,
631    TexcoordParseError,
632    FaceParseError,
633    MaterialParseError,
634    InvalidObjectName,
635    InvalidPolygon,
636    FaceVertexOutOfBounds,
637    FaceTexCoordOutOfBounds,
638    FaceNormalOutOfBounds,
639    FaceColorOutOfBounds,
640    InvalidLoadOptionConfig,
641    GenericFailure,
642}
643
644impl fmt::Display for LoadError {
645    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
646        let msg = match *self {
647            LoadError::OpenFileFailed => "open file failed",
648            LoadError::ReadError => "read error",
649            LoadError::UnrecognizedCharacter => "unrecognized character",
650            LoadError::PositionParseError => "position parse error",
651            LoadError::NormalParseError => "normal parse error",
652            LoadError::TexcoordParseError => "texcoord parse error",
653            LoadError::FaceParseError => "face parse error",
654            LoadError::MaterialParseError => "material parse error",
655            LoadError::InvalidObjectName => "invalid object name",
656            LoadError::InvalidPolygon => "invalid polygon",
657            LoadError::FaceVertexOutOfBounds => "face vertex index out of bounds",
658            LoadError::FaceTexCoordOutOfBounds => "face texcoord index out of bounds",
659            LoadError::FaceNormalOutOfBounds => "face normal index out of bounds",
660            LoadError::FaceColorOutOfBounds => "face vertex color index out of bounds",
661            LoadError::InvalidLoadOptionConfig => "mutually exclusive load options",
662            LoadError::GenericFailure => "generic failure",
663        };
664
665        f.write_str(msg)
666    }
667}
668
669impl Error for LoadError {}
670
671/// A [`Result`] containing all the models loaded from the file and any
672/// materials from referenced material libraries. Or an error that occured while
673/// loading.
674pub type LoadResult = Result<(Vec<Model>, Result<Vec<Material>, LoadError>), LoadError>;
675
676/// A [`Result`] containing all the materials loaded from the file and a map of
677/// `MTL` name to index. Or an error that occured while loading.
678pub type MTLLoadResult = Result<(Vec<Material>, HashMap<String, usize>), LoadError>;
679
680/// Struct storing indices corresponding to the vertex.
681///
682/// Some vertices may not have texture coordinates or normals, 0 is used to
683/// indicate this as OBJ indices begin at 1
684#[derive(Hash, Eq, PartialEq, PartialOrd, Ord, Debug, Copy, Clone)]
685struct VertexIndices {
686    pub v: usize,
687    pub vt: usize,
688    pub vn: usize,
689}
690
691static MISSING_INDEX: usize = usize::MAX;
692
693impl VertexIndices {
694    /// Parse the vertex indices from the face string.
695    ///
696    /// Valid face strings are those that are valid for a Wavefront `OBJ` file.
697    ///
698    /// Also handles relative face indices (negative values) which is why
699    /// passing the number of positions, texcoords and normals is required.
700    ///
701    /// Returns `None` if the face string is invalid.
702    fn parse(
703        face_str: &str,
704        pos_sz: usize,
705        tex_sz: usize,
706        norm_sz: usize,
707    ) -> Option<VertexIndices> {
708        let mut indices = [MISSING_INDEX; 3];
709        for i in face_str.split('/').enumerate() {
710            // Catch case of v//vn where we'll find an empty string in one of our splits
711            // since there are no texcoords for the mesh.
712            if !i.1.is_empty() {
713                match isize::from_str(i.1) {
714                    Ok(x) => {
715                        // Handle relative indices
716                        *indices.get_mut(i.0)? = if x < 0 {
717                            match i.0 {
718                                0 => (pos_sz as isize + x) as _,
719                                1 => (tex_sz as isize + x) as _,
720                                2 => (norm_sz as isize + x) as _,
721                                _ => return None, // Invalid number of elements for a face
722                            }
723                        } else {
724                            (x - 1) as _
725                        };
726                    }
727                    Err(_) => return None,
728                }
729            }
730        }
731        Some(VertexIndices {
732            v: indices[0],
733            vt: indices[1],
734            vn: indices[2],
735        })
736    }
737}
738
739/// Enum representing a face, storing indices for the face vertices.
740#[derive(Debug)]
741enum Face {
742    Point(VertexIndices),
743    Line(VertexIndices, VertexIndices),
744    Triangle(VertexIndices, VertexIndices, VertexIndices),
745    Quad(VertexIndices, VertexIndices, VertexIndices, VertexIndices),
746    Polygon(Vec<VertexIndices>),
747}
748
749/// Parse the float information from the words. Words is an iterator over the
750/// float strings. Returns `false` if parsing failed.
751fn parse_floatn(val_str: &mut SplitWhitespace, vals: &mut Vec<Float>, n: usize) -> bool {
752    let sz = vals.len();
753    for p in val_str.take(n) {
754        match FromStr::from_str(p) {
755            Ok(x) => vals.push(x),
756            Err(_) => return false,
757        }
758    }
759    // Require that we found the desired number of floats.
760    sz + n == vals.len()
761}
762
763/// Parse the a string into a float3 array, returns an error if parsing failed
764fn parse_float3(val_str: SplitWhitespace) -> Result<[Float; 3], LoadError> {
765    let arr: [Float; 3] = val_str
766        .take(3)
767        .map(FromStr::from_str)
768        .collect::<Result<Vec<_>, _>>()
769        .map_err(|_| LoadError::MaterialParseError)?
770        .try_into()
771        .unwrap();
772    Ok(arr)
773}
774
775/// Parse the a string into a float value, returns an error if parsing failed
776fn parse_float(val_str: Option<&str>) -> Result<Float, LoadError> {
777    val_str
778        .map(FromStr::from_str)
779        .map_or(Err(LoadError::MaterialParseError), |v| {
780            v.map_err(|_| LoadError::MaterialParseError)
781        })
782}
783
784/// Parse vertex indices for a face and append it to the list of faces passed.
785///
786/// Also handles relative face indices (negative values) which is why passing
787/// the number of positions, texcoords and normals is required.
788///
789/// Returns `false` if an error occured parsing the face.
790fn parse_face(
791    face_str: SplitWhitespace,
792    faces: &mut Vec<Face>,
793    pos_sz: usize,
794    tex_sz: usize,
795    norm_sz: usize,
796) -> bool {
797    let mut indices = Vec::new();
798    for f in face_str {
799        match VertexIndices::parse(f, pos_sz, tex_sz, norm_sz) {
800            Some(v) => indices.push(v),
801            None => return false,
802        }
803    }
804    // Check what kind face we read and push it on
805    match indices.len() {
806        1 => faces.push(Face::Point(indices[0])),
807        2 => faces.push(Face::Line(indices[0], indices[1])),
808        3 => faces.push(Face::Triangle(indices[0], indices[1], indices[2])),
809        4 => faces.push(Face::Quad(indices[0], indices[1], indices[2], indices[3])),
810        _ => faces.push(Face::Polygon(indices)),
811    }
812    true
813}
814
815/// Add a vertex to a mesh by either re-using an existing index (e.g. it's in
816/// the `index_map`) or appending the position, texcoord and normal as
817/// appropriate and creating a new vertex.
818fn add_vertex(
819    mesh: &mut Mesh,
820    index_map: &mut HashMap<VertexIndices, u32>,
821    vert: &VertexIndices,
822    pos: &[Float],
823    v_color: &[Float],
824    texcoord: &[Float],
825    normal: &[Float],
826) -> Result<(), LoadError> {
827    match index_map.get(vert) {
828        Some(&i) => mesh.indices.push(i),
829        None => {
830            let v = vert.v;
831            if v.saturating_mul(3).saturating_add(2) >= pos.len() {
832                return Err(LoadError::FaceVertexOutOfBounds);
833            }
834            // Add the vertex to the mesh
835            mesh.positions.push(pos[v * 3]);
836            mesh.positions.push(pos[v * 3 + 1]);
837            mesh.positions.push(pos[v * 3 + 2]);
838            if !texcoord.is_empty() && vert.vt != MISSING_INDEX {
839                let vt = vert.vt;
840                if vt * 2 + 1 >= texcoord.len() {
841                    return Err(LoadError::FaceTexCoordOutOfBounds);
842                }
843                mesh.texcoords.push(texcoord[vt * 2]);
844                mesh.texcoords.push(texcoord[vt * 2 + 1]);
845            }
846            if !normal.is_empty() && vert.vn != MISSING_INDEX {
847                let vn = vert.vn;
848                if vn * 3 + 2 >= normal.len() {
849                    return Err(LoadError::FaceNormalOutOfBounds);
850                }
851                mesh.normals.push(normal[vn * 3]);
852                mesh.normals.push(normal[vn * 3 + 1]);
853                mesh.normals.push(normal[vn * 3 + 2]);
854            }
855            if !v_color.is_empty() {
856                if v * 3 + 2 >= v_color.len() {
857                    return Err(LoadError::FaceColorOutOfBounds);
858                }
859                mesh.vertex_color.push(v_color[v * 3]);
860                mesh.vertex_color.push(v_color[v * 3 + 1]);
861                mesh.vertex_color.push(v_color[v * 3 + 2]);
862            }
863            let next = index_map.len() as u32;
864            mesh.indices.push(next);
865            index_map.insert(*vert, next);
866        }
867    }
868    Ok(())
869}
870
871/// Export a list of faces to a mesh and return it, optionally converting quads
872/// to tris.
873fn export_faces(
874    pos: &[Float],
875    v_color: &[Float],
876    texcoord: &[Float],
877    normal: &[Float],
878    faces: &[Face],
879    mat_id: Option<usize>,
880    load_options: &LoadOptions,
881) -> Result<Mesh, LoadError> {
882    let mut index_map = HashMap::new();
883    let mut mesh = Mesh {
884        material_id: mat_id,
885        ..Default::default()
886    };
887    let mut is_all_triangles = true;
888
889    for f in faces {
890        // Optimized paths for Triangles and Quads, Polygon handles the general case of
891        // an unknown length triangle fan.
892        match *f {
893            Face::Point(ref a) => {
894                if !load_options.ignore_points {
895                    add_vertex(&mut mesh, &mut index_map, a, pos, v_color, texcoord, normal)?;
896                    if load_options.triangulate {
897                        add_vertex(&mut mesh, &mut index_map, a, pos, v_color, texcoord, normal)?;
898                        add_vertex(&mut mesh, &mut index_map, a, pos, v_color, texcoord, normal)?;
899                    } else {
900                        is_all_triangles = false;
901                        mesh.face_arities.push(1);
902                    }
903                }
904            }
905            Face::Line(ref a, ref b) => {
906                if !load_options.ignore_lines {
907                    add_vertex(&mut mesh, &mut index_map, a, pos, v_color, texcoord, normal)?;
908                    add_vertex(&mut mesh, &mut index_map, b, pos, v_color, texcoord, normal)?;
909                    if load_options.triangulate {
910                        add_vertex(&mut mesh, &mut index_map, b, pos, v_color, texcoord, normal)?;
911                    } else {
912                        is_all_triangles = false;
913                        mesh.face_arities.push(2);
914                    }
915                }
916            }
917            Face::Triangle(ref a, ref b, ref c) => {
918                add_vertex(&mut mesh, &mut index_map, a, pos, v_color, texcoord, normal)?;
919                add_vertex(&mut mesh, &mut index_map, b, pos, v_color, texcoord, normal)?;
920                add_vertex(&mut mesh, &mut index_map, c, pos, v_color, texcoord, normal)?;
921                if !load_options.triangulate {
922                    mesh.face_arities.push(3);
923                }
924            }
925            Face::Quad(ref a, ref b, ref c, ref d) => {
926                add_vertex(&mut mesh, &mut index_map, a, pos, v_color, texcoord, normal)?;
927                add_vertex(&mut mesh, &mut index_map, b, pos, v_color, texcoord, normal)?;
928                add_vertex(&mut mesh, &mut index_map, c, pos, v_color, texcoord, normal)?;
929
930                if load_options.triangulate {
931                    add_vertex(&mut mesh, &mut index_map, a, pos, v_color, texcoord, normal)?;
932                    add_vertex(&mut mesh, &mut index_map, c, pos, v_color, texcoord, normal)?;
933                    add_vertex(&mut mesh, &mut index_map, d, pos, v_color, texcoord, normal)?;
934                } else {
935                    add_vertex(&mut mesh, &mut index_map, d, pos, v_color, texcoord, normal)?;
936                    is_all_triangles = false;
937                    mesh.face_arities.push(4);
938                }
939            }
940            Face::Polygon(ref indices) => {
941                if load_options.triangulate {
942                    let a = indices.first().ok_or(LoadError::InvalidPolygon)?;
943                    let mut b = indices.get(1).ok_or(LoadError::InvalidPolygon)?;
944                    for c in indices.iter().skip(2) {
945                        add_vertex(&mut mesh, &mut index_map, a, pos, v_color, texcoord, normal)?;
946                        add_vertex(&mut mesh, &mut index_map, b, pos, v_color, texcoord, normal)?;
947                        add_vertex(&mut mesh, &mut index_map, c, pos, v_color, texcoord, normal)?;
948                        b = c;
949                    }
950                } else {
951                    for i in indices.iter() {
952                        add_vertex(&mut mesh, &mut index_map, i, pos, v_color, texcoord, normal)?;
953                    }
954                    is_all_triangles = false;
955                    mesh.face_arities.push(indices.len() as u32);
956                }
957            }
958        }
959    }
960
961    if is_all_triangles {
962        // This is a triangle-only mesh.
963        mesh.face_arities = Vec::new();
964    }
965
966    Ok(mesh)
967}
968
969/// Add a vertex to a mesh by either re-using an existing index (e.g. it's in
970/// the `index_map`) or appending the position, texcoord and normal as
971/// appropriate and creating a new vertex.
972#[allow(clippy::too_many_arguments)]
973#[inline]
974fn add_vertex_multi_index(
975    mesh: &mut Mesh,
976    index_map: &mut HashMap<usize, u32>,
977    normal_index_map: &mut HashMap<usize, u32>,
978    texcoord_index_map: &mut HashMap<usize, u32>,
979    vert: &VertexIndices,
980    pos: &[Float],
981    v_color: &[Float],
982    texcoord: &[Float],
983    normal: &[Float],
984) -> Result<(), LoadError> {
985    match index_map.get(&vert.v) {
986        Some(&i) => mesh.indices.push(i),
987        None => {
988            let vertex = vert.v;
989
990            if vertex.saturating_mul(3).saturating_add(2) >= pos.len() {
991                return Err(LoadError::FaceVertexOutOfBounds);
992            }
993
994            // Add the vertex to the mesh.
995            mesh.positions.push(pos[vertex * 3]);
996            mesh.positions.push(pos[vertex * 3 + 1]);
997            mesh.positions.push(pos[vertex * 3 + 2]);
998
999            let next = index_map.len() as u32;
1000            mesh.indices.push(next);
1001            index_map.insert(vertex, next);
1002
1003            // Also add vertex colors to the mesh if present.
1004            if !v_color.is_empty() {
1005                let vertex = vert.v;
1006
1007                if vertex * 3 + 2 >= v_color.len() {
1008                    return Err(LoadError::FaceColorOutOfBounds);
1009                }
1010
1011                mesh.vertex_color.push(v_color[vertex * 3]);
1012                mesh.vertex_color.push(v_color[vertex * 3 + 1]);
1013                mesh.vertex_color.push(v_color[vertex * 3 + 2]);
1014            }
1015        }
1016    }
1017
1018    if !texcoord.is_empty() {
1019        let texcoord_indices = &mut mesh.texcoord_indices;
1020
1021        if MISSING_INDEX == vert.vt {
1022            // Special case: the very first vertex of the mesh has no index.
1023            if texcoord_indices.is_empty() {
1024                // We have no choice, simply reference the first vertex.
1025                mesh.texcoords.push(texcoord[0]);
1026                mesh.texcoords.push(texcoord[1]);
1027
1028                texcoord_indices.push(0);
1029                texcoord_index_map.insert(0, 0);
1030            // We use the previous index. Not great a fallback but less prone to
1031            // cause issues. FIXME: we should probably check if the
1032            // data is per-vertex-per-face and if so calculate the
1033            // average from adjacent face vertices.
1034            } else {
1035                texcoord_indices.push(*texcoord_indices.last().unwrap());
1036            }
1037        } else {
1038            match texcoord_index_map.get(&vert.vt) {
1039                Some(&index) => mesh.texcoord_indices.push(index as _),
1040                None => {
1041                    let vt = vert.vt;
1042
1043                    if vt * 2 + 1 >= texcoord.len() {
1044                        return Err(LoadError::FaceTexCoordOutOfBounds);
1045                    }
1046
1047                    mesh.texcoords.push(texcoord[vt * 2]);
1048                    mesh.texcoords.push(texcoord[vt * 2 + 1]);
1049
1050                    let next = texcoord_index_map.len() as u32;
1051                    mesh.texcoord_indices.push(next);
1052                    texcoord_index_map.insert(vt, next);
1053                }
1054            }
1055        }
1056    }
1057
1058    if !normal.is_empty() {
1059        let normal_indices = &mut mesh.normal_indices;
1060        // The index is sparse – we need to make up a value.
1061        if MISSING_INDEX == vert.vn {
1062            // Special case: the very first vertex of the mesh has no index.
1063            if normal_indices.is_empty() {
1064                // We have no choice, simply reference the first vertex.
1065                mesh.normals.push(normal[0]);
1066                mesh.normals.push(normal[1]);
1067                mesh.normals.push(normal[2]);
1068
1069                normal_indices.push(0);
1070                normal_index_map.insert(0, 0);
1071            // We use the previous index. Not great a fallback but less prone to
1072            // cause issues. FIXME: we should probably check if the
1073            // data is per-vertex-per-face and if so calculate the
1074            // average from adjacent face vertices.
1075            } else {
1076                normal_indices.push(*normal_indices.last().unwrap());
1077            }
1078        } else {
1079            match normal_index_map.get(&vert.vn) {
1080                Some(&index) => normal_indices.push(index as _),
1081                None => {
1082                    let vn = vert.vn;
1083
1084                    if vn * 3 + 2 >= normal.len() {
1085                        return Err(LoadError::FaceNormalOutOfBounds);
1086                    }
1087
1088                    mesh.normals.push(normal[vn * 3]);
1089                    mesh.normals.push(normal[vn * 3 + 1]);
1090                    mesh.normals.push(normal[vn * 3 + 2]);
1091
1092                    let next = normal_index_map.len() as u32;
1093                    normal_indices.push(next);
1094                    normal_index_map.insert(vn, next);
1095                }
1096            }
1097        }
1098    }
1099
1100    Ok(())
1101}
1102
1103/// Export a list of faces to a mesh and return it, optionally converting quads
1104/// to tris.
1105fn export_faces_multi_index(
1106    pos: &[Float],
1107    v_color: &[Float],
1108    texcoord: &[Float],
1109    normal: &[Float],
1110    faces: &[Face],
1111    mat_id: Option<usize>,
1112    load_options: &LoadOptions,
1113) -> Result<Mesh, LoadError> {
1114    let mut index_map = HashMap::new();
1115    let mut normal_index_map = HashMap::new();
1116    let mut texcoord_index_map = HashMap::new();
1117
1118    let mut mesh = Mesh {
1119        material_id: mat_id,
1120        ..Default::default()
1121    };
1122
1123    let mut is_all_triangles = true;
1124
1125    for f in faces {
1126        // Optimized paths for Triangles and Quads, Polygon handles the general case of
1127        // an unknown length triangle fan
1128        match *f {
1129            Face::Point(ref a) => {
1130                if !load_options.ignore_points {
1131                    add_vertex_multi_index(
1132                        &mut mesh,
1133                        &mut index_map,
1134                        &mut normal_index_map,
1135                        &mut texcoord_index_map,
1136                        a,
1137                        pos,
1138                        v_color,
1139                        texcoord,
1140                        normal,
1141                    )?;
1142                    if load_options.triangulate {
1143                        add_vertex_multi_index(
1144                            &mut mesh,
1145                            &mut index_map,
1146                            &mut normal_index_map,
1147                            &mut texcoord_index_map,
1148                            a,
1149                            pos,
1150                            v_color,
1151                            texcoord,
1152                            normal,
1153                        )?;
1154                        add_vertex_multi_index(
1155                            &mut mesh,
1156                            &mut index_map,
1157                            &mut normal_index_map,
1158                            &mut texcoord_index_map,
1159                            a,
1160                            pos,
1161                            v_color,
1162                            texcoord,
1163                            normal,
1164                        )?;
1165                    } else {
1166                        is_all_triangles = false;
1167                        mesh.face_arities.push(1);
1168                    }
1169                }
1170            }
1171            Face::Line(ref a, ref b) => {
1172                if !load_options.ignore_lines {
1173                    add_vertex_multi_index(
1174                        &mut mesh,
1175                        &mut index_map,
1176                        &mut normal_index_map,
1177                        &mut texcoord_index_map,
1178                        a,
1179                        pos,
1180                        v_color,
1181                        texcoord,
1182                        normal,
1183                    )?;
1184                    add_vertex_multi_index(
1185                        &mut mesh,
1186                        &mut index_map,
1187                        &mut normal_index_map,
1188                        &mut texcoord_index_map,
1189                        b,
1190                        pos,
1191                        v_color,
1192                        texcoord,
1193                        normal,
1194                    )?;
1195                    if load_options.triangulate {
1196                        add_vertex_multi_index(
1197                            &mut mesh,
1198                            &mut index_map,
1199                            &mut normal_index_map,
1200                            &mut texcoord_index_map,
1201                            b,
1202                            pos,
1203                            v_color,
1204                            texcoord,
1205                            normal,
1206                        )?;
1207                    } else {
1208                        is_all_triangles = false;
1209                        mesh.face_arities.push(2);
1210                    }
1211                }
1212            }
1213            Face::Triangle(ref a, ref b, ref c) => {
1214                add_vertex_multi_index(
1215                    &mut mesh,
1216                    &mut index_map,
1217                    &mut normal_index_map,
1218                    &mut texcoord_index_map,
1219                    a,
1220                    pos,
1221                    v_color,
1222                    texcoord,
1223                    normal,
1224                )?;
1225                add_vertex_multi_index(
1226                    &mut mesh,
1227                    &mut index_map,
1228                    &mut normal_index_map,
1229                    &mut texcoord_index_map,
1230                    b,
1231                    pos,
1232                    v_color,
1233                    texcoord,
1234                    normal,
1235                )?;
1236                add_vertex_multi_index(
1237                    &mut mesh,
1238                    &mut index_map,
1239                    &mut normal_index_map,
1240                    &mut texcoord_index_map,
1241                    c,
1242                    pos,
1243                    v_color,
1244                    texcoord,
1245                    normal,
1246                )?;
1247                if !load_options.triangulate {
1248                    mesh.face_arities.push(3);
1249                }
1250            }
1251            Face::Quad(ref a, ref b, ref c, ref d) => {
1252                add_vertex_multi_index(
1253                    &mut mesh,
1254                    &mut index_map,
1255                    &mut normal_index_map,
1256                    &mut texcoord_index_map,
1257                    a,
1258                    pos,
1259                    v_color,
1260                    texcoord,
1261                    normal,
1262                )?;
1263                add_vertex_multi_index(
1264                    &mut mesh,
1265                    &mut index_map,
1266                    &mut normal_index_map,
1267                    &mut texcoord_index_map,
1268                    b,
1269                    pos,
1270                    v_color,
1271                    texcoord,
1272                    normal,
1273                )?;
1274                add_vertex_multi_index(
1275                    &mut mesh,
1276                    &mut index_map,
1277                    &mut normal_index_map,
1278                    &mut texcoord_index_map,
1279                    c,
1280                    pos,
1281                    v_color,
1282                    texcoord,
1283                    normal,
1284                )?;
1285
1286                if load_options.triangulate {
1287                    add_vertex_multi_index(
1288                        &mut mesh,
1289                        &mut index_map,
1290                        &mut normal_index_map,
1291                        &mut texcoord_index_map,
1292                        a,
1293                        pos,
1294                        v_color,
1295                        texcoord,
1296                        normal,
1297                    )?;
1298                    add_vertex_multi_index(
1299                        &mut mesh,
1300                        &mut index_map,
1301                        &mut normal_index_map,
1302                        &mut texcoord_index_map,
1303                        c,
1304                        pos,
1305                        v_color,
1306                        texcoord,
1307                        normal,
1308                    )?;
1309                    add_vertex_multi_index(
1310                        &mut mesh,
1311                        &mut index_map,
1312                        &mut normal_index_map,
1313                        &mut texcoord_index_map,
1314                        d,
1315                        pos,
1316                        v_color,
1317                        texcoord,
1318                        normal,
1319                    )?;
1320                } else {
1321                    add_vertex_multi_index(
1322                        &mut mesh,
1323                        &mut index_map,
1324                        &mut normal_index_map,
1325                        &mut texcoord_index_map,
1326                        d,
1327                        pos,
1328                        v_color,
1329                        texcoord,
1330                        normal,
1331                    )?;
1332                    is_all_triangles = false;
1333                    mesh.face_arities.push(4);
1334                }
1335            }
1336            Face::Polygon(ref indices) => {
1337                if load_options.triangulate {
1338                    let a = indices.first().ok_or(LoadError::InvalidPolygon)?;
1339                    let mut b = indices.get(1).ok_or(LoadError::InvalidPolygon)?;
1340                    for c in indices.iter().skip(2) {
1341                        add_vertex_multi_index(
1342                            &mut mesh,
1343                            &mut index_map,
1344                            &mut normal_index_map,
1345                            &mut texcoord_index_map,
1346                            a,
1347                            pos,
1348                            v_color,
1349                            texcoord,
1350                            normal,
1351                        )?;
1352                        add_vertex_multi_index(
1353                            &mut mesh,
1354                            &mut index_map,
1355                            &mut normal_index_map,
1356                            &mut texcoord_index_map,
1357                            b,
1358                            pos,
1359                            v_color,
1360                            texcoord,
1361                            normal,
1362                        )?;
1363                        add_vertex_multi_index(
1364                            &mut mesh,
1365                            &mut index_map,
1366                            &mut normal_index_map,
1367                            &mut texcoord_index_map,
1368                            c,
1369                            pos,
1370                            v_color,
1371                            texcoord,
1372                            normal,
1373                        )?;
1374                        b = c;
1375                    }
1376                } else {
1377                    for i in indices.iter() {
1378                        add_vertex_multi_index(
1379                            &mut mesh,
1380                            &mut index_map,
1381                            &mut normal_index_map,
1382                            &mut texcoord_index_map,
1383                            i,
1384                            pos,
1385                            v_color,
1386                            texcoord,
1387                            normal,
1388                        )?;
1389                    }
1390                    is_all_triangles = false;
1391                    mesh.face_arities.push(indices.len() as u32);
1392                }
1393            }
1394        }
1395    }
1396
1397    if is_all_triangles {
1398        // This is a triangle-only mesh.
1399        mesh.face_arities = Vec::new();
1400    }
1401
1402    #[cfg(feature = "merging")]
1403    if load_options.merge_identical_points {
1404        if !mesh.vertex_color.is_empty() {
1405            mesh.vertex_color_indices = mesh.indices.clone();
1406            merge_identical_points::<3>(&mut mesh.vertex_color, &mut mesh.vertex_color_indices);
1407        }
1408        merge_identical_points::<3>(&mut mesh.positions, &mut mesh.indices);
1409        merge_identical_points::<3>(&mut mesh.normals, &mut mesh.normal_indices);
1410        merge_identical_points::<2>(&mut mesh.texcoords, &mut mesh.texcoord_indices);
1411    }
1412
1413    #[cfg(feature = "reordering")]
1414    if load_options.reorder_data {
1415        reorder_data(&mut mesh);
1416    }
1417
1418    Ok(mesh)
1419}
1420
1421#[cfg(feature = "reordering")]
1422#[inline]
1423fn reorder_data(mesh: &mut Mesh) {
1424    // If we have per face per vertex data for UVs ...
1425    if mesh.positions.len() < mesh.texcoords.len() {
1426        mesh.texcoords = mesh
1427            .texcoord_indices
1428            .iter()
1429            .flat_map(|&index| {
1430                let index = index as usize * 2;
1431                IntoIterator::into_iter([mesh.texcoords[index], mesh.texcoords[index + 1]])
1432            })
1433            .collect::<Vec<_>>();
1434    } else {
1435        assert!(mesh.texcoords.len() == mesh.positions.len());
1436
1437        let mut new_texcoords = vec![0.0; mesh.positions.len()];
1438        mesh.texcoord_indices
1439            .iter()
1440            .zip(&mesh.indices)
1441            .for_each(|(&texcoord_index, &index)| {
1442                let texcoord_index = texcoord_index as usize * 2;
1443                let index = index as usize * 2;
1444                new_texcoords[index] = mesh.texcoords[texcoord_index];
1445                new_texcoords[index + 1] = mesh.texcoords[texcoord_index + 1];
1446            });
1447
1448        mesh.texcoords = new_texcoords;
1449    }
1450
1451    // Clear indices.
1452    mesh.texcoord_indices = Vec::new();
1453
1454    // If we have per face per vertex data for normals ...
1455    if mesh.positions.len() < mesh.normals.len() {
1456        mesh.normals = mesh
1457            .normal_indices
1458            .iter()
1459            .flat_map(|&index| {
1460                let index = index as usize * 2;
1461                IntoIterator::into_iter([
1462                    mesh.normals[index],
1463                    mesh.normals[index + 1],
1464                    mesh.normals[index + 2],
1465                ])
1466            })
1467            .collect::<Vec<_>>();
1468    } else {
1469        assert!(mesh.normals.len() == mesh.positions.len());
1470
1471        let mut new_normals = vec![0.0; mesh.positions.len()];
1472        mesh.normal_indices
1473            .iter()
1474            .zip(&mesh.indices)
1475            .for_each(|(&normal_index, &index)| {
1476                let normal_index = normal_index as usize * 3;
1477                let index = index as usize * 3;
1478                new_normals[index] = mesh.normals[normal_index];
1479                new_normals[index + 1] = mesh.normals[normal_index + 1];
1480                new_normals[index + 2] = mesh.normals[normal_index + 2];
1481            });
1482
1483        mesh.normals = new_normals;
1484    }
1485
1486    // Clear indices.
1487    mesh.normal_indices = Vec::new();
1488}
1489
1490/// Merge identical points. A point has dimension N.
1491#[cfg(feature = "merging")]
1492#[inline]
1493fn merge_identical_points<const N: usize>(points: &mut Vec<Float>, indices: &mut Vec<u32>)
1494where
1495    [(); size_of::<[Float; N]>()]:,
1496{
1497    if indices.is_empty() {
1498        return;
1499    }
1500
1501    let mut compressed_indices = Vec::new();
1502    let mut canonical_indices = HashMap::<[u8; size_of::<[Float; N]>()], u32>::new();
1503
1504    let mut index = 0;
1505    *points = points
1506        .chunks(N)
1507        .filter_map(|position| {
1508            let position: &[Float; N] = &unsafe { *(position.as_ptr() as *const [Float; N]) };
1509
1510            // Ugly, but floats have no Eq and no Hash.
1511            let bitpattern = unsafe {
1512                std::mem::transmute::<&[Float; N], &[u8; size_of::<[Float; N]>()]>(position)
1513            };
1514
1515            match canonical_indices.get(bitpattern) {
1516                Some(&other_index) => {
1517                    compressed_indices.push(other_index);
1518                    None
1519                }
1520                None => {
1521                    canonical_indices.insert(*bitpattern, index);
1522                    compressed_indices.push(index);
1523                    index += 1;
1524                    Some(IntoIterator::into_iter(*position))
1525                }
1526            }
1527        })
1528        .flatten()
1529        .collect();
1530
1531    indices
1532        .iter_mut()
1533        .for_each(|vertex| *vertex = compressed_indices[*vertex as usize]);
1534}
1535
1536#[derive(Debug)]
1537struct TmpModels {
1538    models: Vec<Model>,
1539    pos: Vec<Float>,
1540    v_color: Vec<Float>,
1541    texcoord: Vec<Float>,
1542    normal: Vec<Float>,
1543    faces: Vec<Face>,
1544    // name of the current object being parsed
1545    name: String,
1546    // material used by the current object being parsed
1547    mat_id: Option<usize>,
1548}
1549
1550impl Default for TmpModels {
1551    #[inline]
1552    fn default() -> Self {
1553        Self {
1554            models: Vec::new(),
1555            pos: Vec::new(),
1556            v_color: Vec::new(),
1557            texcoord: Vec::new(),
1558            normal: Vec::new(),
1559            faces: Vec::new(),
1560            name: "unnamed_object".to_owned(),
1561            mat_id: None,
1562        }
1563    }
1564}
1565
1566impl TmpModels {
1567    #[inline]
1568    fn new() -> Self {
1569        Self::default()
1570    }
1571
1572    #[inline]
1573    fn pop_model(&mut self, load_options: &LoadOptions) -> Result<(), LoadError> {
1574        self.models.push(Model::new(
1575            if load_options.single_index {
1576                export_faces(
1577                    &self.pos,
1578                    &self.v_color,
1579                    &self.texcoord,
1580                    &self.normal,
1581                    &self.faces,
1582                    self.mat_id,
1583                    load_options,
1584                )?
1585            } else {
1586                export_faces_multi_index(
1587                    &self.pos,
1588                    &self.v_color,
1589                    &self.texcoord,
1590                    &self.normal,
1591                    &self.faces,
1592                    self.mat_id,
1593                    load_options,
1594                )?
1595            },
1596            self.name.clone(),
1597        ));
1598        self.faces.clear();
1599        Ok(())
1600    }
1601
1602    #[inline]
1603    fn into_models(self) -> Vec<Model> {
1604        self.models
1605    }
1606}
1607
1608#[derive(Debug)]
1609struct TmpMaterials {
1610    materials: Vec<Material>,
1611    mat_map: HashMap<String, usize>,
1612    mtlerr: Option<LoadError>,
1613}
1614
1615impl Default for TmpMaterials {
1616    #[inline]
1617    fn default() -> Self {
1618        Self {
1619            materials: Vec::new(),
1620            mat_map: HashMap::new(),
1621            mtlerr: None,
1622        }
1623    }
1624}
1625
1626impl TmpMaterials {
1627    #[inline]
1628    fn new() -> Self {
1629        Self::default()
1630    }
1631
1632    #[inline]
1633    fn push(&mut self, material: Material) {
1634        self.mat_map
1635            .insert(material.name.clone(), self.materials.len());
1636        self.materials.push(material);
1637    }
1638
1639    #[inline]
1640    fn merge(&mut self, mtl_load_result: MTLLoadResult) {
1641        match mtl_load_result {
1642            Ok((mut mats, map)) => {
1643                // Merge the loaded material lib with any currently loaded ones,
1644                // offsetting the indices of the appended
1645                // materials by our current length
1646                let mat_offset = self.materials.len();
1647                self.materials.append(&mut mats);
1648                for m in map {
1649                    self.mat_map.insert(m.0, m.1 + mat_offset);
1650                }
1651            }
1652            Err(e) => {
1653                self.mtlerr = Some(e);
1654            }
1655        }
1656    }
1657
1658    #[inline]
1659    fn into_mtl_load_result(self) -> MTLLoadResult {
1660        Ok((self.materials, self.mat_map))
1661    }
1662
1663    #[inline]
1664    fn into_materials(self) -> Result<Vec<Material>, LoadError> {
1665        if !self.materials.is_empty() {
1666            Ok(self.materials)
1667        } else if let Some(mtlerr) = self.mtlerr {
1668            Err(mtlerr)
1669        } else {
1670            Ok(Vec::new())
1671        }
1672    }
1673}
1674
1675enum ParseReturnType {
1676    LoadMaterial(PathBuf),
1677    None,
1678}
1679
1680#[inline]
1681fn parse_obj_line(
1682    line: std::io::Result<String>,
1683    load_options: &LoadOptions,
1684    models: &mut TmpModels,
1685    materials: &TmpMaterials,
1686) -> Result<ParseReturnType, LoadError> {
1687    let (line, mut words) = match line {
1688        Ok(ref line) => (&line[..], line[..].split_whitespace()),
1689        Err(_e) => {
1690            #[cfg(feature = "log")]
1691            log::error!("load_obj - failed to read line due to {}", _e);
1692            return Err(LoadError::ReadError);
1693        }
1694    };
1695    match words.next() {
1696        Some("#") | None => Ok(ParseReturnType::None),
1697        Some("v") => {
1698            if !parse_floatn(&mut words, &mut models.pos, 3) {
1699                return Err(LoadError::PositionParseError);
1700            }
1701
1702            // Add inline vertex colors if present.
1703            parse_floatn(&mut words, &mut models.v_color, 3);
1704            Ok(ParseReturnType::None)
1705        }
1706        Some("vt") => {
1707            if !parse_floatn(&mut words, &mut models.texcoord, 2) {
1708                Err(LoadError::TexcoordParseError)
1709            } else {
1710                Ok(ParseReturnType::None)
1711            }
1712        }
1713        Some("vn") => {
1714            if !parse_floatn(&mut words, &mut models.normal, 3) {
1715                Err(LoadError::NormalParseError)
1716            } else {
1717                Ok(ParseReturnType::None)
1718            }
1719        }
1720        Some("f") | Some("l") => {
1721            if !parse_face(
1722                words,
1723                &mut models.faces,
1724                models.pos.len() / 3,
1725                models.texcoord.len() / 2,
1726                models.normal.len() / 3,
1727            ) {
1728                Err(LoadError::FaceParseError)
1729            } else {
1730                Ok(ParseReturnType::None)
1731            }
1732        }
1733        // Just treating object and group tags identically. Should there be different behavior
1734        // for them?
1735        Some("o") | Some("g") => {
1736            // If we were already parsing an object then a new object name
1737            // signals the end of the current one, so push it onto our list of objects
1738            if !models.faces.is_empty() {
1739                models.pop_model(load_options)?;
1740            }
1741            let size = line.chars().next().unwrap().len_utf8();
1742            models.name = line[size..].trim().to_owned();
1743            if models.name.is_empty() {
1744                models.name = "unnamed_object".to_owned();
1745            }
1746            Ok(ParseReturnType::None)
1747        }
1748        Some("mtllib") => {
1749            // File name can include spaces so we cannot rely on a SplitWhitespace iterator
1750            let mtllib = line.split_once(' ').unwrap_or_default().1.trim();
1751            let mat_file = Path::new(mtllib).to_path_buf();
1752            Ok(ParseReturnType::LoadMaterial(mat_file))
1753        }
1754        Some("usemtl") => {
1755            let mat_name = line.split_once(' ').unwrap_or_default().1.trim().to_owned();
1756
1757            if !mat_name.is_empty() {
1758                let new_mat = materials.mat_map.get(&mat_name).cloned();
1759                // As materials are returned per-model, a new material within an object
1760                // has to emit a new model with the same name but different material
1761                if models.mat_id != new_mat && !models.faces.is_empty() {
1762                    models.pop_model(load_options)?;
1763                }
1764                if new_mat.is_none() {
1765                    #[cfg(feature = "log")]
1766                    log::warn!(
1767                        "Object {} refers to unfound material: {}",
1768                        models.name,
1769                        mat_name
1770                    );
1771                }
1772                models.mat_id = new_mat;
1773                Ok(ParseReturnType::None)
1774            } else {
1775                Err(LoadError::MaterialParseError)
1776            }
1777        }
1778        // Just ignore unrecognized characters
1779        Some(_) => Ok(ParseReturnType::None),
1780    }
1781}
1782
1783#[inline]
1784fn parse_mtl_line(
1785    line: std::io::Result<String>,
1786    materials: &mut TmpMaterials,
1787    mut cur_mat: Material,
1788) -> Result<Material, LoadError> {
1789    let (line, mut words) = match line {
1790        Ok(ref line) => (line.trim(), line[..].split_whitespace()),
1791        Err(_e) => {
1792            #[cfg(feature = "log")]
1793            log::error!("load_obj - failed to read line due to {}", _e);
1794            return Err(LoadError::ReadError);
1795        }
1796    };
1797
1798    match words.next() {
1799        Some("#") | None => {}
1800        Some("newmtl") => {
1801            // If we were passing a material save it out to our vector
1802            if !cur_mat.name.is_empty() {
1803                materials.push(cur_mat);
1804            }
1805            cur_mat = Material::default();
1806            cur_mat.name = line[6..].trim().to_owned();
1807            if cur_mat.name.is_empty() {
1808                return Err(LoadError::InvalidObjectName);
1809            }
1810        }
1811        Some("Ka") => cur_mat.ambient = Some(parse_float3(words)?),
1812        Some("Kd") => cur_mat.diffuse = Some(parse_float3(words)?),
1813        Some("Ks") => cur_mat.specular = Some(parse_float3(words)?),
1814        Some("Ns") => cur_mat.shininess = Some(parse_float(words.next())?),
1815        Some("Ni") => cur_mat.optical_density = Some(parse_float(words.next())?),
1816        Some("d") => cur_mat.dissolve = Some(parse_float(words.next())?),
1817        Some("map_Ka") => match line.get(6..).map(str::trim) {
1818            Some("") | None => return Err(LoadError::MaterialParseError),
1819            Some(tex) => cur_mat.ambient_texture = Some(tex.to_owned()),
1820        },
1821        Some("map_Kd") => match line.get(6..).map(str::trim) {
1822            Some("") | None => return Err(LoadError::MaterialParseError),
1823            Some(tex) => cur_mat.diffuse_texture = Some(tex.to_owned()),
1824        },
1825        Some("map_Ks") => match line.get(6..).map(str::trim) {
1826            Some("") | None => return Err(LoadError::MaterialParseError),
1827            Some(tex) => cur_mat.specular_texture = Some(tex.to_owned()),
1828        },
1829        Some("map_Bump") | Some("map_bump") => match line.get(8..).map(str::trim) {
1830            Some("") | None => return Err(LoadError::MaterialParseError),
1831            Some(tex) => cur_mat.normal_texture = Some(tex.to_owned()),
1832        },
1833        Some("map_Ns") | Some("map_ns") | Some("map_NS") => match line.get(6..).map(str::trim) {
1834            Some("") | None => return Err(LoadError::MaterialParseError),
1835            Some(tex) => cur_mat.shininess_texture = Some(tex.to_owned()),
1836        },
1837        Some("bump") => match line.get(4..).map(str::trim) {
1838            Some("") | None => return Err(LoadError::MaterialParseError),
1839            Some(tex) => cur_mat.normal_texture = Some(tex.to_owned()),
1840        },
1841        Some("map_d") => match line.get(5..).map(str::trim) {
1842            Some("") | None => return Err(LoadError::MaterialParseError),
1843            Some(tex) => cur_mat.dissolve_texture = Some(tex.to_owned()),
1844        },
1845        Some("illum") => {
1846            if let Some(p) = words.next() {
1847                match FromStr::from_str(p) {
1848                    Ok(x) => cur_mat.illumination_model = Some(x),
1849                    Err(_) => return Err(LoadError::MaterialParseError),
1850                }
1851            } else {
1852                return Err(LoadError::MaterialParseError);
1853            }
1854        }
1855        Some(unknown) => {
1856            if !unknown.is_empty() {
1857                let param = line[unknown.len()..].trim().to_owned();
1858                cur_mat.unknown_param.insert(unknown.to_owned(), param);
1859            }
1860        }
1861    }
1862    Ok(cur_mat)
1863}
1864
1865/// Load the various objects specified in the `OBJ` file and any associated
1866/// `MTL` file.
1867///
1868/// Returns a pair of `Vec`s containing the loaded models and materials from the
1869/// file.
1870///
1871/// # Arguments
1872///
1873/// * `load_options` – Governs on-the-fly processing of the mesh during loading.
1874///   See [`LoadOptions`] for more information.
1875pub fn load_obj<P>(file_name: P, load_options: &LoadOptions) -> LoadResult
1876where
1877    P: AsRef<Path> + fmt::Debug,
1878{
1879    let file = match File::open(file_name.as_ref()) {
1880        Ok(f) => f,
1881        Err(_e) => {
1882            #[cfg(feature = "log")]
1883            log::error!("load_obj - failed to open {:?} due to {}", file_name, _e);
1884            return Err(LoadError::OpenFileFailed);
1885        }
1886    };
1887    let mut reader = BufReader::new(file);
1888    load_obj_buf(&mut reader, load_options, |mat_path| {
1889        let full_path = if let Some(parent) = file_name.as_ref().parent() {
1890            parent.join(mat_path)
1891        } else {
1892            mat_path.to_owned()
1893        };
1894
1895        self::load_mtl(full_path)
1896    })
1897}
1898
1899/// Load the materials defined in a `MTL` file.
1900///
1901/// Returns a pair with a `Vec` holding all loaded materials and a `HashMap`
1902/// containing a mapping of material names to indices in the Vec.
1903pub fn load_mtl<P>(file_name: P) -> MTLLoadResult
1904where
1905    P: AsRef<Path> + fmt::Debug,
1906{
1907    let file = match File::open(file_name.as_ref()) {
1908        Ok(f) => f,
1909        Err(_e) => {
1910            #[cfg(feature = "log")]
1911            log::error!("load_mtl - failed to open {:?} due to {}", file_name, _e);
1912            return Err(LoadError::OpenFileFailed);
1913        }
1914    };
1915    let mut reader = BufReader::new(file);
1916    load_mtl_buf(&mut reader)
1917}
1918
1919/// Load the various meshes in an `OBJ` buffer.
1920///
1921/// This could e.g. be a network stream, a text file already in memory etc.
1922///
1923/// # Arguments
1924///
1925/// You must pass a `material_loader` function, which will return a material
1926/// given a name.
1927///
1928/// A trivial material loader may just look at the file name and then call
1929/// `load_mtl_buf` with the in-memory MTL file source.
1930///
1931/// Alternatively it could pass an `MTL` file in memory to `load_mtl_buf` to
1932/// parse materials from some buffer.
1933///
1934/// * `load_options` – Governs on-the-fly processing of the mesh during loading.
1935///   See [`LoadOptions`] for more information.
1936///
1937/// # Example
1938/// The test for `load_obj_buf` includes the OBJ and MTL files as strings
1939/// and uses a `Cursor` to provide a `BufRead` interface on the buffer.
1940///
1941/// ```
1942/// use std::{env, fs::File, io::BufReader};
1943///
1944/// let dir = env::current_dir().unwrap();
1945/// let mut cornell_box_obj = dir.clone();
1946/// cornell_box_obj.push("obj/cornell_box.obj");
1947/// let mut cornell_box_file = BufReader::new(File::open(cornell_box_obj.as_path()).unwrap());
1948///
1949/// let mut cornell_box_mtl1 = dir.clone();
1950/// cornell_box_mtl1.push("obj/cornell_box.mtl");
1951///
1952/// let mut cornell_box_mtl2 = dir.clone();
1953/// cornell_box_mtl2.push("obj/cornell_box2.mtl");
1954///
1955/// let m = tobj::load_obj_buf(
1956///     &mut cornell_box_file,
1957///     &tobj::LoadOptions {
1958///         triangulate: true,
1959///         single_index: true,
1960///         ..Default::default()
1961///     },
1962///     |p| match p.file_name().unwrap().to_str().unwrap() {
1963///         "cornell_box.mtl" => {
1964///             let f = File::open(cornell_box_mtl1.as_path()).unwrap();
1965///             tobj::load_mtl_buf(&mut BufReader::new(f))
1966///         }
1967///         "cornell_box2.mtl" => {
1968///             let f = File::open(cornell_box_mtl2.as_path()).unwrap();
1969///             tobj::load_mtl_buf(&mut BufReader::new(f))
1970///         }
1971///         _ => unreachable!(),
1972///     },
1973/// );
1974/// ```
1975pub fn load_obj_buf<B, ML>(
1976    reader: &mut B,
1977    load_options: &LoadOptions,
1978    material_loader: ML,
1979) -> LoadResult
1980where
1981    B: BufRead,
1982    ML: Fn(&Path) -> MTLLoadResult,
1983{
1984    if !load_options.is_valid() {
1985        return Err(LoadError::InvalidLoadOptionConfig);
1986    }
1987
1988    let mut models = TmpModels::new();
1989    let mut materials = TmpMaterials::new();
1990
1991    for line in reader.lines() {
1992        let parse_return = parse_obj_line(line, load_options, &mut models, &materials)?;
1993        match parse_return {
1994            ParseReturnType::LoadMaterial(mat_file) => {
1995                materials.merge(material_loader(mat_file.as_path()));
1996            }
1997            ParseReturnType::None => {}
1998        }
1999    }
2000
2001    // For the last object in the file we won't encounter another object name to
2002    // tell us when it's done, so if we're parsing an object push the last one
2003    // on the list as well
2004    models.pop_model(load_options)?;
2005
2006    Ok((models.into_models(), materials.into_materials()))
2007}
2008
2009/// Load the various materials in a `MTL` buffer.
2010pub fn load_mtl_buf<B: BufRead>(reader: &mut B) -> MTLLoadResult {
2011    let mut materials = TmpMaterials::new();
2012    // The current material being parsed
2013    let mut cur_mat = Material::default();
2014
2015    for line in reader.lines() {
2016        cur_mat = parse_mtl_line(line, &mut materials, cur_mat)?;
2017    }
2018
2019    // Finalize the last material we were parsing
2020    if !cur_mat.name.is_empty() {
2021        materials.push(cur_mat);
2022    }
2023
2024    materials.into_mtl_load_result()
2025}
2026
2027#[cfg(feature = "async")]
2028/// Load the various meshes in an `OBJ` buffer.
2029///
2030/// This could e.g. be a text file already in memory, a file loaded
2031///  asynchronously over the network etc.
2032///
2033/// <div class="warning">
2034///
2035/// This function is not fully async, as it does not use async reader objects. This means you
2036/// must either use a blocking reader object, which negates the point of async in the first place,
2037/// or you must asynchronously read the entire buffer into memory, and then give an in-memory reader
2038/// to this function, which is wasteful with memory and not terribly efficient.
2039///
2040/// Instead, it is recommended to use crate-specific feature flag support to enable support for
2041/// various third-party async readers. For example, you can enable the `tokio` feature flag to
2042/// use [tokio::load_obj_buf()].
2043///
2044/// </div>
2045///
2046/// # Arguments
2047///
2048/// You must pass a `material_loader` function, which will return a future
2049/// that loads a material given a name.
2050///
2051/// A trivial material loader may just look at the file name and then call
2052/// `load_mtl_buf` with the in-memory MTL file source.
2053///
2054/// Alternatively it could pass an `MTL` file in memory to `load_mtl_buf` to
2055/// parse materials from some buffer.
2056///
2057/// * `load_options` – Governs on-the-fly processing of the mesh during loading.
2058///   See [`LoadOptions`] for more information.
2059///
2060/// # Example
2061/// The test for `load_obj_buf` includes the OBJ and MTL files as strings
2062/// and uses a `Cursor` to provide a `BufRead` interface on the buffer.
2063///
2064/// ```
2065/// async {
2066///     use std::{env, fs::File, io::BufReader};
2067///
2068///     let dir = env::current_dir().unwrap();
2069///     let mut cornell_box_obj = dir.clone();
2070///     cornell_box_obj.push("obj/cornell_box.obj");
2071///     let mut cornell_box_file = BufReader::new(File::open(cornell_box_obj.as_path()).unwrap());
2072///
2073///     let m =
2074///         tobj::load_obj_buf_async(&mut cornell_box_file, &tobj::GPU_LOAD_OPTIONS, move |p| {
2075///             let dir_clone = dir.clone();
2076///             async move {
2077///                 let mut cornell_box_mtl1 = dir_clone.clone();
2078///                 cornell_box_mtl1.push("obj/cornell_box.mtl");
2079///
2080///                 let mut cornell_box_mtl2 = dir_clone.clone();
2081///                 cornell_box_mtl2.push("obj/cornell_box2.mtl");
2082///
2083///                 match p.as_str() {
2084///                     "cornell_box.mtl" => {
2085///                         let f = File::open(cornell_box_mtl1.as_path()).unwrap();
2086///                         tobj::load_mtl_buf(&mut BufReader::new(f))
2087///                     }
2088///                     "cornell_box2.mtl" => {
2089///                         let f = File::open(cornell_box_mtl2.as_path()).unwrap();
2090///                         tobj::load_mtl_buf(&mut BufReader::new(f))
2091///                     }
2092///                     _ => unreachable!(),
2093///                 }
2094///             }
2095///         })
2096///         .await;
2097/// };
2098/// ```
2099#[deprecated(
2100    since = "4.0.3",
2101    note = "load_obj_buf_async is not fully async. Use futures/tokio feature flags instead"
2102)]
2103pub async fn load_obj_buf_async<B, ML, MLFut>(
2104    reader: &mut B,
2105    load_options: &LoadOptions,
2106    material_loader: ML,
2107) -> LoadResult
2108where
2109    B: BufRead,
2110    ML: Fn(String) -> MLFut,
2111    MLFut: Future<Output = MTLLoadResult>,
2112{
2113    if !load_options.is_valid() {
2114        return Err(LoadError::InvalidLoadOptionConfig);
2115    }
2116
2117    let mut models = TmpModels::new();
2118    let mut materials = TmpMaterials::new();
2119
2120    for line in reader.lines() {
2121        let parse_return = parse_obj_line(line, load_options, &mut models, &materials)?;
2122        match parse_return {
2123            ParseReturnType::LoadMaterial(mat_file) => {
2124                match mat_file.into_os_string().into_string() {
2125                    Ok(mat_file) => materials.merge(material_loader(mat_file).await),
2126                    Err(_mat_file) => {
2127                        #[cfg(feature = "log")]
2128                        log::error!(
2129                            "load_obj - material path contains invalid Unicode: {_mat_file:?}"
2130                        );
2131                        return Err(LoadError::ReadError);
2132                    }
2133                }
2134            }
2135            ParseReturnType::None => {}
2136        }
2137    }
2138
2139    // For the last object in the file we won't encounter another object name to
2140    // tell us when it's done, so if we're parsing an object push the last one
2141    // on the list as well
2142    models.pop_model(load_options)?;
2143
2144    Ok((models.into_models(), materials.into_materials()))
2145}
2146
2147/// Optional module supporting async loading with `futures` traits.
2148///
2149/// The functions in this module are drop-in replacements for the standard non-async functions in
2150/// this crate, but tailored to use [futures](https://crates.io/crates/futures)
2151/// [AsyncRead](futures_lite::AsyncRead) traits.
2152///
2153/// While `futures` provides basic read/write async traits, it does *not* provide filesystem IO
2154/// implementations for these traits, so this module only contains `*_buf()` variants of this
2155/// crate's functions.
2156#[cfg(feature = "futures")]
2157pub mod futures {
2158    use super::*;
2159
2160    use futures_lite::{pin, AsyncBufRead, AsyncBufReadExt, StreamExt};
2161
2162    /// Asynchronously load the various meshes in an 'OBJ' buffer.
2163    ///
2164    /// This functions exactly like [crate::load_obj_buf()], but uses async read traits and an async
2165    /// `material_loader` function. See [crate::load_obj_buf()] for more.
2166    ///
2167    /// This is the [futures](https://crates.io/crates/futures) variant of `load_obj_buf()`; see
2168    /// [module-level](futures) documentation for more.
2169    ///
2170    /// # Examples
2171    /// ```
2172    /// use futures_lite::io::BufReader;
2173    ///
2174    /// const CORNELL_BOX_OBJ: &[u8] = include_bytes!("../obj/cornell_box.obj");
2175    /// const CORNELL_BOX_MTL1: &[u8] = include_bytes!("../obj/cornell_box.mtl");
2176    /// const CORNELL_BOX_MTL2: &[u8] = include_bytes!("../obj/cornell_box2.mtl");
2177    ///
2178    /// # async fn wrapper() {
2179    /// let m = tobj::futures::load_obj_buf(
2180    ///     BufReader::new(CORNELL_BOX_OBJ),
2181    ///     &tobj::LoadOptions {
2182    ///         triangulate: true,
2183    ///         single_index: true,
2184    ///         ..Default::default()
2185    ///     },
2186    ///     |p| async move {
2187    ///         match p.to_str().unwrap() {
2188    ///             "cornell_box.mtl" => {
2189    ///                 let r = BufReader::new(CORNELL_BOX_MTL1);
2190    ///                 tobj::futures::load_mtl_buf(r).await
2191    ///             }
2192    ///             "cornell_box2.mtl" => {
2193    ///                 let r = BufReader::new(CORNELL_BOX_MTL2);
2194    ///                 tobj::futures::load_mtl_buf(r).await
2195    ///             }
2196    ///             _ => unreachable!(),
2197    ///         }
2198    ///     },
2199    /// ).await;
2200    /// # }
2201    /// ```
2202    pub async fn load_obj_buf<B, ML, MLFut>(
2203        reader: B,
2204        load_options: &LoadOptions,
2205        material_loader: ML,
2206    ) -> LoadResult
2207    where
2208        B: AsyncBufRead,
2209        ML: Fn(PathBuf) -> MLFut,
2210        MLFut: Future<Output = MTLLoadResult>,
2211    {
2212        if !load_options.is_valid() {
2213            return Err(LoadError::InvalidLoadOptionConfig);
2214        }
2215
2216        let mut models = TmpModels::new();
2217        let mut materials = TmpMaterials::new();
2218
2219        pin!(reader);
2220        let mut lines = reader.lines();
2221        while let Some(line) = lines.next().await {
2222            let parse_return = parse_obj_line(line, load_options, &mut models, &materials)?;
2223            match parse_return {
2224                ParseReturnType::LoadMaterial(mat_file) => {
2225                    materials.merge(material_loader(mat_file).await);
2226                }
2227                ParseReturnType::None => {}
2228            }
2229        }
2230
2231        // For the last object in the file we won't encounter another object name to
2232        // tell us when it's done, so if we're parsing an object push the last one
2233        // on the list as well
2234        models.pop_model(load_options)?;
2235
2236        Ok((models.into_models(), materials.into_materials()))
2237    }
2238
2239    /// Asynchronously load the various materials in a `MTL` buffer.
2240    ///
2241    /// This is the [futures](https://crates.io/crates/futures) variant of `load_mtl_buf()`; see
2242    /// [module-level](futures) documentation for more.
2243    pub async fn load_mtl_buf<B: AsyncBufRead>(reader: B) -> MTLLoadResult {
2244        let mut materials = TmpMaterials::new();
2245        // The current material being parsed
2246        let mut cur_mat = Material::default();
2247
2248        pin!(reader);
2249        let mut lines = reader.lines();
2250        while let Some(line) = lines.next().await {
2251            cur_mat = parse_mtl_line(line, &mut materials, cur_mat)?;
2252        }
2253
2254        // Finalize the last material we were parsing
2255        if !cur_mat.name.is_empty() {
2256            materials.push(cur_mat);
2257        }
2258
2259        materials.into_mtl_load_result()
2260    }
2261}
2262
2263/// Optional module supporting async loading with `tokio` traits.
2264///
2265/// The functions in this module are drop-in replacements for the standard non-async functions in
2266/// this crate, but tailored to use [tokio](https://crates.io/crates/tokio)
2267/// [AsyncRead](::tokio::io::AsyncRead) traits.
2268#[cfg(feature = "tokio")]
2269pub mod tokio {
2270    use super::*;
2271
2272    use ::tokio::fs::File;
2273    use ::tokio::io::{AsyncBufRead, AsyncBufReadExt, BufReader};
2274    use ::tokio::pin;
2275
2276    /// Load the various objects specified in the `OBJ` file and any associated `MTL` file.
2277    ///
2278    /// This functions exactly like [crate::load_obj()] but uses async filesystem logic. See
2279    /// [crate::load_obj()] for more.
2280    ///
2281    /// This is the [tokio](https://crates.io/crates/tokio) variant of `load_obj()`; see
2282    /// [module-level](tokio) documentation for more.
2283    pub async fn load_obj<P>(file_name: P, load_options: &LoadOptions) -> LoadResult
2284    where
2285        P: AsRef<Path> + fmt::Debug,
2286    {
2287        let file = match File::open(file_name.as_ref()).await {
2288            Ok(f) => f,
2289            Err(_e) => {
2290                #[cfg(feature = "log")]
2291                log::error!("load_obj - failed to open {:?} due to {}", file_name, _e);
2292                return Err(LoadError::OpenFileFailed);
2293            }
2294        };
2295        load_obj_buf(BufReader::new(file), load_options, |mat_path| {
2296            // This needs to be "copied" into this closure before moving it into the async one below
2297            let file_name: &Path = file_name.as_ref();
2298            let file_name = file_name.to_path_buf();
2299            async move {
2300                let full_path = if let Some(parent) = file_name.parent() {
2301                    parent.join(mat_path)
2302                } else {
2303                    mat_path
2304                };
2305
2306                load_mtl(full_path).await
2307            }
2308        })
2309        .await
2310    }
2311
2312    /// Load the materials defined in a `MTL` file.
2313    ///
2314    /// This functions exactly like [crate::load_mtl()] but uses async filesystem logic. See
2315    /// [crate::load_mtl()] for more.
2316    ///
2317    /// This is the [tokio](https://crates.io/crates/tokio) variant of `load_mtl()`; see
2318    /// [module-level](tokio) documentation for more.
2319    pub async fn load_mtl<P>(file_name: P) -> MTLLoadResult
2320    where
2321        P: AsRef<Path> + fmt::Debug,
2322    {
2323        let file = match File::open(file_name.as_ref()).await {
2324            Ok(f) => f,
2325            Err(_e) => {
2326                #[cfg(feature = "log")]
2327                log::error!("load_mtl - failed to open {:?} due to {}", file_name, _e);
2328                return Err(LoadError::OpenFileFailed);
2329            }
2330        };
2331        load_mtl_buf(BufReader::new(file)).await
2332    }
2333
2334    /// Asynchronously load the various meshes in an 'OBJ' buffer.
2335    ///
2336    /// This functions exactly like [crate::load_obj_buf()], but uses async read traits and an async
2337    /// `material_loader` function. See [crate::load_obj_buf()] for more.
2338    ///
2339    /// This is the [tokio](https://crates.io/crates/tokio) variant of `load_obj_buf()`; see
2340    /// [module-level](tokio) documentation for more.
2341    pub async fn load_obj_buf<B, ML, MLFut>(
2342        reader: B,
2343        load_options: &LoadOptions,
2344        material_loader: ML,
2345    ) -> LoadResult
2346    where
2347        B: AsyncBufRead,
2348        ML: Fn(PathBuf) -> MLFut,
2349        MLFut: Future<Output = MTLLoadResult>,
2350    {
2351        if !load_options.is_valid() {
2352            return Err(LoadError::InvalidLoadOptionConfig);
2353        }
2354
2355        let mut models = TmpModels::new();
2356        let mut materials = TmpMaterials::new();
2357
2358        pin!(reader);
2359        let mut lines = reader.lines();
2360        while let Some(line) = lines.next_line().await.transpose() {
2361            let parse_return = parse_obj_line(line, load_options, &mut models, &materials)?;
2362            match parse_return {
2363                ParseReturnType::LoadMaterial(mat_file) => {
2364                    materials.merge(material_loader(mat_file).await);
2365                }
2366                ParseReturnType::None => {}
2367            }
2368        }
2369
2370        // For the last object in the file we won't encounter another object name to
2371        // tell us when it's done, so if we're parsing an object push the last one
2372        // on the list as well
2373        models.pop_model(load_options)?;
2374
2375        Ok((models.into_models(), materials.into_materials()))
2376    }
2377
2378    /// Asynchronously load the various materials in a `MTL` buffer.
2379    ///
2380    /// This is the [tokio](https://crates.io/crates/tokio) variant of `load_mtl_buf()`; see
2381    /// [module-level](tokio) documentation for more.
2382    pub async fn load_mtl_buf<B: AsyncBufRead>(reader: B) -> MTLLoadResult {
2383        let mut materials = TmpMaterials::new();
2384        // The current material being parsed
2385        let mut cur_mat = Material::default();
2386
2387        pin!(reader);
2388        let mut lines = reader.lines();
2389        while let Some(line) = lines.next_line().await.transpose() {
2390            cur_mat = parse_mtl_line(line, &mut materials, cur_mat)?;
2391        }
2392
2393        // Finalize the last material we were parsing
2394        if !cur_mat.name.is_empty() {
2395            materials.push(cur_mat);
2396        }
2397
2398        materials.into_mtl_load_result()
2399    }
2400}