Skip to main content

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