1 /*
2 The MIT License (MIT)
3 
4 Copyright (c) 2012-Present, Syoyo Fujita and many contributors.
5 
6 Permission is hereby granted, free of charge, to any person obtaining a copy
7 of this software and associated documentation files (the "Software"), to deal
8 in the Software without restriction, including without limitation the rights
9 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 copies of the Software, and to permit persons to whom the Software is
11 furnished to do so, subject to the following conditions:
12 
13 The above copyright notice and this permission notice shall be included in
14 all copies or substantial portions of the Software.
15 
16 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 THE SOFTWARE.
23 */
24 
25 //
26 // version 2.0.0 : Add new object oriented API. 1.x API is still provided.
27 //                 * Support line primitive.
28 //                 * Support points primitive.
29 //                 * Support multiple search path for .mtl(v1 API).
30 //                 * Support vertex weight `vw`(as an tinyobj extension)
31 //                 * Support escaped whitespece in mtllib
32 // version 1.4.0 : Modifed ParseTextureNameAndOption API
33 // version 1.3.1 : Make ParseTextureNameAndOption API public
34 // version 1.3.0 : Separate warning and error message(breaking API of LoadObj)
35 // version 1.2.3 : Added color space extension('-colorspace') to tex opts.
36 // version 1.2.2 : Parse multiple group names.
37 // version 1.2.1 : Added initial support for line('l') primitive(PR #178)
38 // version 1.2.0 : Hardened implementation(#175)
39 // version 1.1.1 : Support smoothing groups(#162)
40 // version 1.1.0 : Support parsing vertex color(#144)
41 // version 1.0.8 : Fix parsing `g` tag just after `usemtl`(#138)
42 // version 1.0.7 : Support multiple tex options(#126)
43 // version 1.0.6 : Add TINYOBJLOADER_USE_DOUBLE option(#124)
44 // version 1.0.5 : Ignore `Tr` when `d` exists in MTL(#43)
45 // version 1.0.4 : Support multiple filenames for 'mtllib'(#112)
46 // version 1.0.3 : Support parsing texture options(#85)
47 // version 1.0.2 : Improve parsing speed by about a factor of 2 for large
48 // files(#105)
49 // version 1.0.1 : Fixes a shape is lost if obj ends with a 'usemtl'(#104)
50 // version 1.0.0 : Change data structure. Change license from BSD to MIT.
51 //
52 
53 //
54 // Use this in *one* .cc
55 //   #define TINYOBJLOADER_IMPLEMENTATION
56 //   #include "tiny_obj_loader.h"
57 //
58 
59 #ifndef TINY_OBJ_LOADER_H_
60 #define TINY_OBJ_LOADER_H_
61 
62 #include <map>
63 #include <string>
64 #include <vector>
65 
66 namespace tinyobj {
67 
68 // TODO(syoyo): Better C++11 detection for older compiler
69 #if __cplusplus > 199711L
70 #define TINYOBJ_OVERRIDE override
71 #else
72 #define TINYOBJ_OVERRIDE
73 #endif
74 
75 #ifdef __clang__
76 #pragma clang diagnostic push
77 #if __has_warning("-Wzero-as-null-pointer-constant")
78 #pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant"
79 #endif
80 
81 #pragma clang diagnostic ignored "-Wpadded"
82 
83 #endif
84 
85 // https://en.wikipedia.org/wiki/Wavefront_.obj_file says ...
86 //
87 //  -blendu on | off                       # set horizontal texture blending
88 //  (default on)
89 //  -blendv on | off                       # set vertical texture blending
90 //  (default on)
91 //  -boost real_value                      # boost mip-map sharpness
92 //  -mm base_value gain_value              # modify texture map values (default
93 //  0 1)
94 //                                         #     base_value = brightness,
95 //                                         gain_value = contrast
96 //  -o u [v [w]]                           # Origin offset             (default
97 //  0 0 0)
98 //  -s u [v [w]]                           # Scale                     (default
99 //  1 1 1)
100 //  -t u [v [w]]                           # Turbulence                (default
101 //  0 0 0)
102 //  -texres resolution                     # texture resolution to create
103 //  -clamp on | off                        # only render texels in the clamped
104 //  0-1 range (default off)
105 //                                         #   When unclamped, textures are
106 //                                         repeated across a surface,
107 //                                         #   when clamped, only texels which
108 //                                         fall within the 0-1
109 //                                         #   range are rendered.
110 //  -bm mult_value                         # bump multiplier (for bump maps
111 //  only)
112 //
113 //  -imfchan r | g | b | m | l | z         # specifies which channel of the file
114 //  is used to
115 //                                         # create a scalar or bump texture.
116 //                                         r:red, g:green,
117 //                                         # b:blue, m:matte, l:luminance,
118 //                                         z:z-depth..
119 //                                         # (the default for bump is 'l' and
120 //                                         for decal is 'm')
121 //  bump -imfchan r bumpmap.tga            # says to use the red channel of
122 //  bumpmap.tga as the bumpmap
123 //
124 // For reflection maps...
125 //
126 //   -type sphere                           # specifies a sphere for a "refl"
127 //   reflection map
128 //   -type cube_top    | cube_bottom |      # when using a cube map, the texture
129 //   file for each
130 //         cube_front  | cube_back   |      # side of the cube is specified
131 //         separately
132 //         cube_left   | cube_right
133 //
134 // TinyObjLoader extension.
135 //
136 //   -colorspace SPACE                      # Color space of the texture. e.g.
137 //   'sRGB` or 'linear'
138 //
139 
140 #ifdef TINYOBJLOADER_USE_DOUBLE
141 //#pragma message "using double"
142 typedef double real_t;
143 #else
144 //#pragma message "using float"
145 typedef float real_t;
146 #endif
147 
148 typedef enum {
149   TEXTURE_TYPE_NONE,  // default
150   TEXTURE_TYPE_SPHERE,
151   TEXTURE_TYPE_CUBE_TOP,
152   TEXTURE_TYPE_CUBE_BOTTOM,
153   TEXTURE_TYPE_CUBE_FRONT,
154   TEXTURE_TYPE_CUBE_BACK,
155   TEXTURE_TYPE_CUBE_LEFT,
156   TEXTURE_TYPE_CUBE_RIGHT
157 } texture_type_t;
158 
159 struct texture_option_t {
160   texture_type_t type;      // -type (default TEXTURE_TYPE_NONE)
161   real_t sharpness;         // -boost (default 1.0?)
162   real_t brightness;        // base_value in -mm option (default 0)
163   real_t contrast;          // gain_value in -mm option (default 1)
164   real_t origin_offset[3];  // -o u [v [w]] (default 0 0 0)
165   real_t scale[3];          // -s u [v [w]] (default 1 1 1)
166   real_t turbulence[3];     // -t u [v [w]] (default 0 0 0)
167   int texture_resolution;   // -texres resolution (No default value in the spec.
168                             // We'll use -1)
169   bool clamp;               // -clamp (default false)
170   char imfchan;  // -imfchan (the default for bump is 'l' and for decal is 'm')
171   bool blendu;   // -blendu (default on)
172   bool blendv;   // -blendv (default on)
173   real_t bump_multiplier;  // -bm (for bump maps only, default 1.0)
174 
175   // extension
176   std::string colorspace;  // Explicitly specify color space of stored texel
177                            // value. Usually `sRGB` or `linear` (default empty).
178 };
179 
180 struct material_t {
181   std::string name;
182 
183   real_t ambient[3];
184   real_t diffuse[3];
185   real_t specular[3];
186   real_t transmittance[3];
187   real_t emission[3];
188   real_t shininess;
189   real_t ior;       // index of refraction
190   real_t dissolve;  // 1 == opaque; 0 == fully transparent
191   // illumination model (see http://www.fileformat.info/format/material/)
192   int illum;
193 
194   int dummy;  // Suppress padding warning.
195 
196   std::string ambient_texname;             // map_Ka
197   std::string diffuse_texname;             // map_Kd
198   std::string specular_texname;            // map_Ks
199   std::string specular_highlight_texname;  // map_Ns
200   std::string bump_texname;                // map_bump, map_Bump, bump
201   std::string displacement_texname;        // disp
202   std::string alpha_texname;               // map_d
203   std::string reflection_texname;          // refl
204 
205   texture_option_t ambient_texopt;
206   texture_option_t diffuse_texopt;
207   texture_option_t specular_texopt;
208   texture_option_t specular_highlight_texopt;
209   texture_option_t bump_texopt;
210   texture_option_t displacement_texopt;
211   texture_option_t alpha_texopt;
212   texture_option_t reflection_texopt;
213 
214   // PBR extension
215   // http://exocortex.com/blog/extending_wavefront_mtl_to_support_pbr
216   real_t roughness;            // [0, 1] default 0
217   real_t metallic;             // [0, 1] default 0
218   real_t sheen;                // [0, 1] default 0
219   real_t clearcoat_thickness;  // [0, 1] default 0
220   real_t clearcoat_roughness;  // [0, 1] default 0
221   real_t anisotropy;           // aniso. [0, 1] default 0
222   real_t anisotropy_rotation;  // anisor. [0, 1] default 0
223   real_t pad0;
224   std::string roughness_texname;  // map_Pr
225   std::string metallic_texname;   // map_Pm
226   std::string sheen_texname;      // map_Ps
227   std::string emissive_texname;   // map_Ke
228   std::string normal_texname;     // norm. For normal mapping.
229 
230   texture_option_t roughness_texopt;
231   texture_option_t metallic_texopt;
232   texture_option_t sheen_texopt;
233   texture_option_t emissive_texopt;
234   texture_option_t normal_texopt;
235 
236   int pad2;
237 
238   std::map<std::string, std::string> unknown_parameter;
239 
240 #ifdef TINY_OBJ_LOADER_PYTHON_BINDING
241   // For pybind11
GetDiffusematerial_t242   std::array<double, 3> GetDiffuse() {
243     std::array<double, 3> values;
244     values[0] = double(diffuse[0]);
245     values[1] = double(diffuse[1]);
246     values[2] = double(diffuse[2]);
247 
248     return values;
249   }
250 
GetSpecularmaterial_t251   std::array<double, 3> GetSpecular() {
252     std::array<double, 3> values;
253     values[0] = double(specular[0]);
254     values[1] = double(specular[1]);
255     values[2] = double(specular[2]);
256 
257     return values;
258   }
259 
GetTransmittancematerial_t260   std::array<double, 3> GetTransmittance() {
261     std::array<double, 3> values;
262     values[0] = double(transmittance[0]);
263     values[1] = double(transmittance[1]);
264     values[2] = double(transmittance[2]);
265 
266     return values;
267   }
268 
GetEmissionmaterial_t269   std::array<double, 3> GetEmission() {
270     std::array<double, 3> values;
271     values[0] = double(emission[0]);
272     values[1] = double(emission[1]);
273     values[2] = double(emission[2]);
274 
275     return values;
276   }
277 
GetAmbientmaterial_t278   std::array<double, 3> GetAmbient() {
279     std::array<double, 3> values;
280     values[0] = double(ambient[0]);
281     values[1] = double(ambient[1]);
282     values[2] = double(ambient[2]);
283 
284     return values;
285   }
286 
SetDiffusematerial_t287   void SetDiffuse(std::array<double, 3> &a) {
288     diffuse[0] = real_t(a[0]);
289     diffuse[1] = real_t(a[1]);
290     diffuse[2] = real_t(a[2]);
291   }
292 
SetAmbientmaterial_t293   void SetAmbient(std::array<double, 3> &a) {
294     ambient[0] = real_t(a[0]);
295     ambient[1] = real_t(a[1]);
296     ambient[2] = real_t(a[2]);
297   }
298 
SetSpecularmaterial_t299   void SetSpecular(std::array<double, 3> &a) {
300     specular[0] = real_t(a[0]);
301     specular[1] = real_t(a[1]);
302     specular[2] = real_t(a[2]);
303   }
304 
SetTransmittancematerial_t305   void SetTransmittance(std::array<double, 3> &a) {
306     transmittance[0] = real_t(a[0]);
307     transmittance[1] = real_t(a[1]);
308     transmittance[2] = real_t(a[2]);
309   }
310 
GetCustomParametermaterial_t311   std::string GetCustomParameter(const std::string &key) {
312     std::map<std::string, std::string>::const_iterator it =
313         unknown_parameter.find(key);
314 
315     if (it != unknown_parameter.end()) {
316       return it->second;
317     }
318     return std::string();
319   }
320 
321 #endif
322 };
323 
324 struct tag_t {
325   std::string name;
326 
327   std::vector<int> intValues;
328   std::vector<real_t> floatValues;
329   std::vector<std::string> stringValues;
330 };
331 
332 struct joint_and_weight_t {
333   int joint_id;
334   real_t weight;
335 };
336 
337 struct skin_weight_t {
338   int vertex_id;  // Corresponding vertex index in `attrib_t::vertices`.
339                   // Compared to `index_t`, this index must be positive and
340                   // start with 0(does not allow relative indexing)
341   std::vector<joint_and_weight_t> weightValues;
342 };
343 
344 // Index struct to support different indices for vtx/normal/texcoord.
345 // -1 means not used.
346 struct index_t {
347   int vertex_index;
348   int normal_index;
349   int texcoord_index;
350 };
351 
352 struct mesh_t {
353   std::vector<index_t> indices;
354   std::vector<unsigned char>
355       num_face_vertices;          // The number of vertices per
356                                   // face. 3 = triangle, 4 = quad,
357                                   // ... Up to 255 vertices per face.
358   std::vector<int> material_ids;  // per-face material ID
359   std::vector<unsigned int> smoothing_group_ids;  // per-face smoothing group
360                                                   // ID(0 = off. positive value
361                                                   // = group id)
362   std::vector<tag_t> tags;                        // SubD tag
363 };
364 
365 // struct path_t {
366 //  std::vector<int> indices;  // pairs of indices for lines
367 //};
368 
369 struct lines_t {
370   // Linear flattened indices.
371   std::vector<index_t> indices;        // indices for vertices(poly lines)
372   std::vector<int> num_line_vertices;  // The number of vertices per line.
373 };
374 
375 struct points_t {
376   std::vector<index_t> indices;  // indices for points
377 };
378 
379 struct shape_t {
380   std::string name;
381   mesh_t mesh;
382   lines_t lines;
383   points_t points;
384 };
385 
386 // Vertex attributes
387 struct attrib_t {
388   std::vector<real_t> vertices;  // 'v'(xyz)
389 
390   // For backward compatibility, we store vertex weight in separate array.
391   std::vector<real_t> vertex_weights;  // 'v'(w)
392   std::vector<real_t> normals;         // 'vn'
393   std::vector<real_t> texcoords;       // 'vt'(uv)
394 
395   // For backward compatibility, we store texture coordinate 'w' in separate
396   // array.
397   std::vector<real_t> texcoord_ws;  // 'vt'(w)
398   std::vector<real_t> colors;       // extension: vertex colors
399 
400   //
401   // TinyObj extension.
402   //
403 
404   // NOTE(syoyo): array index is based on the appearance order.
405   // To get a corresponding skin weight for a specific vertex id `vid`,
406   // Need to reconstruct a look up table: `skin_weight_t::vertex_id` == `vid`
407   // (e.g. using std::map, std::unordered_map)
408   std::vector<skin_weight_t> skin_weights;
409 
attrib_tattrib_t410   attrib_t() {}
411 
412   //
413   // For pybind11
414   //
GetVerticesattrib_t415   const std::vector<real_t> &GetVertices() const { return vertices; }
416 
GetVertexWeightsattrib_t417   const std::vector<real_t> &GetVertexWeights() const { return vertex_weights; }
418 };
419 
420 struct callback_t {
421   // W is optional and set to 1 if there is no `w` item in `v` line
422   void (*vertex_cb)(void *user_data, real_t x, real_t y, real_t z, real_t w);
423   void (*normal_cb)(void *user_data, real_t x, real_t y, real_t z);
424 
425   // y and z are optional and set to 0 if there is no `y` and/or `z` item(s) in
426   // `vt` line.
427   void (*texcoord_cb)(void *user_data, real_t x, real_t y, real_t z);
428 
429   // called per 'f' line. num_indices is the number of face indices(e.g. 3 for
430   // triangle, 4 for quad)
431   // 0 will be passed for undefined index in index_t members.
432   void (*index_cb)(void *user_data, index_t *indices, int num_indices);
433   // `name` material name, `material_id` = the array index of material_t[]. -1
434   // if
435   // a material not found in .mtl
436   void (*usemtl_cb)(void *user_data, const char *name, int material_id);
437   // `materials` = parsed material data.
438   void (*mtllib_cb)(void *user_data, const material_t *materials,
439                     int num_materials);
440   // There may be multiple group names
441   void (*group_cb)(void *user_data, const char **names, int num_names);
442   void (*object_cb)(void *user_data, const char *name);
443 
callback_tcallback_t444   callback_t()
445       : vertex_cb(NULL),
446         normal_cb(NULL),
447         texcoord_cb(NULL),
448         index_cb(NULL),
449         usemtl_cb(NULL),
450         mtllib_cb(NULL),
451         group_cb(NULL),
452         object_cb(NULL) {}
453 };
454 
455 class MaterialReader {
456  public:
MaterialReader()457   MaterialReader() {}
458   virtual ~MaterialReader();
459 
460   virtual bool operator()(const std::string &matId,
461                           std::vector<material_t> *materials,
462                           std::map<std::string, int> *matMap, std::string *warn,
463                           std::string *err) = 0;
464 };
465 
466 ///
467 /// Read .mtl from a file.
468 ///
469 class MaterialFileReader : public MaterialReader {
470  public:
471   // Path could contain separator(';' in Windows, ':' in Posix)
MaterialFileReader(const std::string & mtl_basedir)472   explicit MaterialFileReader(const std::string &mtl_basedir)
473       : m_mtlBaseDir(mtl_basedir) {}
~MaterialFileReader()474   virtual ~MaterialFileReader() TINYOBJ_OVERRIDE {}
475   virtual bool operator()(const std::string &matId,
476                           std::vector<material_t> *materials,
477                           std::map<std::string, int> *matMap, std::string *warn,
478                           std::string *err) TINYOBJ_OVERRIDE;
479 
480  private:
481   std::string m_mtlBaseDir;
482 };
483 
484 ///
485 /// Read .mtl from a stream.
486 ///
487 class MaterialStreamReader : public MaterialReader {
488  public:
MaterialStreamReader(std::istream & inStream)489   explicit MaterialStreamReader(std::istream &inStream)
490       : m_inStream(inStream) {}
~MaterialStreamReader()491   virtual ~MaterialStreamReader() TINYOBJ_OVERRIDE {}
492   virtual bool operator()(const std::string &matId,
493                           std::vector<material_t> *materials,
494                           std::map<std::string, int> *matMap, std::string *warn,
495                           std::string *err) TINYOBJ_OVERRIDE;
496 
497  private:
498   std::istream &m_inStream;
499 };
500 
501 // v2 API
502 struct ObjReaderConfig {
503   bool triangulate;  // triangulate polygon?
504 
505   /// Parse vertex color.
506   /// If vertex color is not present, its filled with default value.
507   /// false = no vertex color
508   /// This will increase memory of parsed .obj
509   bool vertex_color;
510 
511   ///
512   /// Search path to .mtl file.
513   /// Default = "" = search from the same directory of .obj file.
514   /// Valid only when loading .obj from a file.
515   ///
516   std::string mtl_search_path;
517 
ObjReaderConfigObjReaderConfig518   ObjReaderConfig() : triangulate(true), vertex_color(true) {}
519 };
520 
521 ///
522 /// Wavefront .obj reader class(v2 API)
523 ///
524 class ObjReader {
525  public:
ObjReader()526   ObjReader() : valid_(false) {}
~ObjReader()527   ~ObjReader() {}
528 
529   ///
530   /// Load .obj and .mtl from a file.
531   ///
532   /// @param[in] filename wavefront .obj filename
533   /// @param[in] config Reader configuration
534   ///
535   bool ParseFromFile(const std::string &filename,
536                      const ObjReaderConfig &config = ObjReaderConfig());
537 
538   ///
539   /// Parse .obj from a text string.
540   /// Need to supply .mtl text string by `mtl_text`.
541   /// This function ignores `mtllib` line in .obj text.
542   ///
543   /// @param[in] obj_text wavefront .obj filename
544   /// @param[in] mtl_text wavefront .mtl filename
545   /// @param[in] config Reader configuration
546   ///
547   bool ParseFromString(const std::string &obj_text, const std::string &mtl_text,
548                        const ObjReaderConfig &config = ObjReaderConfig());
549 
550   ///
551   /// .obj was loaded or parsed correctly.
552   ///
Valid()553   bool Valid() const { return valid_; }
554 
GetAttrib()555   const attrib_t &GetAttrib() const { return attrib_; }
556 
GetShapes()557   const std::vector<shape_t> &GetShapes() const { return shapes_; }
558 
GetMaterials()559   const std::vector<material_t> &GetMaterials() const { return materials_; }
560 
561   ///
562   /// Warning message(may be filled after `Load` or `Parse`)
563   ///
Warning()564   const std::string &Warning() const { return warning_; }
565 
566   ///
567   /// Error message(filled when `Load` or `Parse` failed)
568   ///
Error()569   const std::string &Error() const { return error_; }
570 
571  private:
572   bool valid_;
573 
574   attrib_t attrib_;
575   std::vector<shape_t> shapes_;
576   std::vector<material_t> materials_;
577 
578   std::string warning_;
579   std::string error_;
580 };
581 
582 /// ==>>========= Legacy v1 API =============================================
583 
584 /// Loads .obj from a file.
585 /// 'attrib', 'shapes' and 'materials' will be filled with parsed shape data
586 /// 'shapes' will be filled with parsed shape data
587 /// Returns true when loading .obj become success.
588 /// Returns warning message into `warn`, and error message into `err`
589 /// 'mtl_basedir' is optional, and used for base directory for .mtl file.
590 /// In default(`NULL'), .mtl file is searched from an application's working
591 /// directory.
592 /// 'triangulate' is optional, and used whether triangulate polygon face in .obj
593 /// or not.
594 /// Option 'default_vcols_fallback' specifies whether vertex colors should
595 /// always be defined, even if no colors are given (fallback to white).
596 bool LoadObj(attrib_t *attrib, std::vector<shape_t> *shapes,
597              std::vector<material_t> *materials, std::string *warn,
598              std::string *err, const char *filename,
599              const char *mtl_basedir = NULL, bool triangulate = true,
600              bool default_vcols_fallback = true);
601 
602 /// Loads .obj from a file with custom user callback.
603 /// .mtl is loaded as usual and parsed material_t data will be passed to
604 /// `callback.mtllib_cb`.
605 /// Returns true when loading .obj/.mtl become success.
606 /// Returns warning message into `warn`, and error message into `err`
607 /// See `examples/callback_api/` for how to use this function.
608 bool LoadObjWithCallback(std::istream &inStream, const callback_t &callback,
609                          void *user_data = NULL,
610                          MaterialReader *readMatFn = NULL,
611                          std::string *warn = NULL, std::string *err = NULL);
612 
613 /// Loads object from a std::istream, uses `readMatFn` to retrieve
614 /// std::istream for materials.
615 /// Returns true when loading .obj become success.
616 /// Returns warning and error message into `err`
617 bool LoadObj(attrib_t *attrib, std::vector<shape_t> *shapes,
618              std::vector<material_t> *materials, std::string *warn,
619              std::string *err, std::istream *inStream,
620              MaterialReader *readMatFn = NULL, bool triangulate = true,
621              bool default_vcols_fallback = true);
622 
623 /// Loads materials into std::map
624 void LoadMtl(std::map<std::string, int> *material_map,
625              std::vector<material_t> *materials, std::istream *inStream,
626              std::string *warning, std::string *err);
627 
628 ///
629 /// Parse texture name and texture option for custom texture parameter through
630 /// material::unknown_parameter
631 ///
632 /// @param[out] texname Parsed texture name
633 /// @param[out] texopt Parsed texopt
634 /// @param[in] linebuf Input string
635 ///
636 bool ParseTextureNameAndOption(std::string *texname, texture_option_t *texopt,
637                                const char *linebuf);
638 
639 /// =<<========== Legacy v1 API =============================================
640 
641 }  // namespace tinyobj
642 
643 #endif  // TINY_OBJ_LOADER_H_
644 
645 #ifdef TINYOBJLOADER_IMPLEMENTATION
646 #include <cassert>
647 #include <cctype>
648 #include <cmath>
649 #include <cstddef>
650 #include <cstdlib>
651 #include <cstring>
652 #include <fstream>
653 #include <limits>
654 #include <sstream>
655 #include <utility>
656 
657 namespace tinyobj {
658 
~MaterialReader()659 MaterialReader::~MaterialReader() {}
660 
661 struct vertex_index_t {
662   int v_idx, vt_idx, vn_idx;
vertex_index_tvertex_index_t663   vertex_index_t() : v_idx(-1), vt_idx(-1), vn_idx(-1) {}
vertex_index_tvertex_index_t664   explicit vertex_index_t(int idx) : v_idx(idx), vt_idx(idx), vn_idx(idx) {}
vertex_index_tvertex_index_t665   vertex_index_t(int vidx, int vtidx, int vnidx)
666       : v_idx(vidx), vt_idx(vtidx), vn_idx(vnidx) {}
667 };
668 
669 // Internal data structure for face representation
670 // index + smoothing group.
671 struct face_t {
672   unsigned int
673       smoothing_group_id;  // smoothing group id. 0 = smoothing groupd is off.
674   int pad_;
675   std::vector<vertex_index_t> vertex_indices;  // face vertex indices.
676 
face_tface_t677   face_t() : smoothing_group_id(0), pad_(0) {}
678 };
679 
680 // Internal data structure for line representation
681 struct __line_t {
682   // l v1/vt1 v2/vt2 ...
683   // In the specification, line primitrive does not have normal index, but
684   // TinyObjLoader allow it
685   std::vector<vertex_index_t> vertex_indices;
686 };
687 
688 // Internal data structure for points representation
689 struct __points_t {
690   // p v1 v2 ...
691   // In the specification, point primitrive does not have normal index and
692   // texture coord index, but TinyObjLoader allow it.
693   std::vector<vertex_index_t> vertex_indices;
694 };
695 
696 struct tag_sizes {
tag_sizestag_sizes697   tag_sizes() : num_ints(0), num_reals(0), num_strings(0) {}
698   int num_ints;
699   int num_reals;
700   int num_strings;
701 };
702 
703 struct obj_shape {
704   std::vector<real_t> v;
705   std::vector<real_t> vn;
706   std::vector<real_t> vt;
707 };
708 
709 //
710 // Manages group of primitives(face, line, points, ...)
711 struct PrimGroup {
712   std::vector<face_t> faceGroup;
713   std::vector<__line_t> lineGroup;
714   std::vector<__points_t> pointsGroup;
715 
clearPrimGroup716   void clear() {
717     faceGroup.clear();
718     lineGroup.clear();
719     pointsGroup.clear();
720   }
721 
IsEmptyPrimGroup722   bool IsEmpty() const {
723     return faceGroup.empty() && lineGroup.empty() && pointsGroup.empty();
724   }
725 
726   // TODO(syoyo): bspline, surface, ...
727 };
728 
729 // See
730 // http://stackoverflow.com/questions/6089231/getting-std-ifstream-to-handle-lf-cr-and-crlf
safeGetline(std::istream & is,std::string & t)731 static std::istream &safeGetline(std::istream &is, std::string &t) {
732   t.clear();
733 
734   // The characters in the stream are read one-by-one using a std::streambuf.
735   // That is faster than reading them one-by-one using the std::istream.
736   // Code that uses streambuf this way must be guarded by a sentry object.
737   // The sentry object performs various tasks,
738   // such as thread synchronization and updating the stream state.
739 
740   std::istream::sentry se(is, true);
741   std::streambuf *sb = is.rdbuf();
742 
743   if (se) {
744     for (;;) {
745       int c = sb->sbumpc();
746       switch (c) {
747         case '\n':
748           return is;
749         case '\r':
750           if (sb->sgetc() == '\n') sb->sbumpc();
751           return is;
752         case EOF:
753           // Also handle the case when the last line has no line ending
754           if (t.empty()) is.setstate(std::ios::eofbit);
755           return is;
756         default:
757           t += static_cast<char>(c);
758       }
759     }
760   }
761 
762   return is;
763 }
764 
765 #define IS_SPACE(x) (((x) == ' ') || ((x) == '\t'))
766 #define IS_DIGIT(x) \
767   (static_cast<unsigned int>((x) - '0') < static_cast<unsigned int>(10))
768 #define IS_NEW_LINE(x) (((x) == '\r') || ((x) == '\n') || ((x) == '\0'))
769 
770 // Make index zero-base, and also support relative index.
fixIndex(int idx,int n,int * ret)771 static inline bool fixIndex(int idx, int n, int *ret) {
772   if (!ret) {
773     return false;
774   }
775 
776   if (idx > 0) {
777     (*ret) = idx - 1;
778     return true;
779   }
780 
781   if (idx == 0) {
782     // zero is not allowed according to the spec.
783     return false;
784   }
785 
786   if (idx < 0) {
787     (*ret) = n + idx;  // negative value = relative
788     return true;
789   }
790 
791   return false;  // never reach here.
792 }
793 
parseString(const char ** token)794 static inline std::string parseString(const char **token) {
795   std::string s;
796   (*token) += strspn((*token), " \t");
797   size_t e = strcspn((*token), " \t\r");
798   s = std::string((*token), &(*token)[e]);
799   (*token) += e;
800   return s;
801 }
802 
parseInt(const char ** token)803 static inline int parseInt(const char **token) {
804   (*token) += strspn((*token), " \t");
805   int i = atoi((*token));
806   (*token) += strcspn((*token), " \t\r");
807   return i;
808 }
809 
810 // Tries to parse a floating point number located at s.
811 //
812 // s_end should be a location in the string where reading should absolutely
813 // stop. For example at the end of the string, to prevent buffer overflows.
814 //
815 // Parses the following EBNF grammar:
816 //   sign    = "+" | "-" ;
817 //   END     = ? anything not in digit ?
818 //   digit   = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" ;
819 //   integer = [sign] , digit , {digit} ;
820 //   decimal = integer , ["." , integer] ;
821 //   float   = ( decimal , END ) | ( decimal , ("E" | "e") , integer , END ) ;
822 //
823 //  Valid strings are for example:
824 //   -0  +3.1417e+2  -0.0E-3  1.0324  -1.41   11e2
825 //
826 // If the parsing is a success, result is set to the parsed value and true
827 // is returned.
828 //
829 // The function is greedy and will parse until any of the following happens:
830 //  - a non-conforming character is encountered.
831 //  - s_end is reached.
832 //
833 // The following situations triggers a failure:
834 //  - s >= s_end.
835 //  - parse failure.
836 //
tryParseDouble(const char * s,const char * s_end,double * result)837 static bool tryParseDouble(const char *s, const char *s_end, double *result) {
838   if (s >= s_end) {
839     return false;
840   }
841 
842   double mantissa = 0.0;
843   // This exponent is base 2 rather than 10.
844   // However the exponent we parse is supposed to be one of ten,
845   // thus we must take care to convert the exponent/and or the
846   // mantissa to a * 2^E, where a is the mantissa and E is the
847   // exponent.
848   // To get the final double we will use ldexp, it requires the
849   // exponent to be in base 2.
850   int exponent = 0;
851 
852   // NOTE: THESE MUST BE DECLARED HERE SINCE WE ARE NOT ALLOWED
853   // TO JUMP OVER DEFINITIONS.
854   char sign = '+';
855   char exp_sign = '+';
856   char const *curr = s;
857 
858   // How many characters were read in a loop.
859   int read = 0;
860   // Tells whether a loop terminated due to reaching s_end.
861   bool end_not_reached = false;
862   bool leading_decimal_dots = false;
863 
864   /*
865           BEGIN PARSING.
866   */
867 
868   // Find out what sign we've got.
869   if (*curr == '+' || *curr == '-') {
870     sign = *curr;
871     curr++;
872     if ((curr != s_end) && (*curr == '.')) {
873       // accept. Somethig like `.7e+2`, `-.5234`
874       leading_decimal_dots = true;
875     }
876   } else if (IS_DIGIT(*curr)) { /* Pass through. */
877   } else if (*curr == '.') {
878     // accept. Somethig like `.7e+2`, `-.5234`
879     leading_decimal_dots = true;
880   } else {
881     goto fail;
882   }
883 
884   // Read the integer part.
885   end_not_reached = (curr != s_end);
886   if (!leading_decimal_dots) {
887     while (end_not_reached && IS_DIGIT(*curr)) {
888       mantissa *= 10;
889       mantissa += static_cast<int>(*curr - 0x30);
890       curr++;
891       read++;
892       end_not_reached = (curr != s_end);
893     }
894 
895     // We must make sure we actually got something.
896     if (read == 0) goto fail;
897   }
898 
899   // We allow numbers of form "#", "###" etc.
900   if (!end_not_reached) goto assemble;
901 
902   // Read the decimal part.
903   if (*curr == '.') {
904     curr++;
905     read = 1;
906     end_not_reached = (curr != s_end);
907     while (end_not_reached && IS_DIGIT(*curr)) {
908       static const double pow_lut[] = {
909           1.0, 0.1, 0.01, 0.001, 0.0001, 0.00001, 0.000001, 0.0000001,
910       };
911       const int lut_entries = sizeof pow_lut / sizeof pow_lut[0];
912 
913       // NOTE: Don't use powf here, it will absolutely murder precision.
914       mantissa += static_cast<int>(*curr - 0x30) *
915                   (read < lut_entries ? pow_lut[read] : std::pow(10.0, -read));
916       read++;
917       curr++;
918       end_not_reached = (curr != s_end);
919     }
920   } else if (*curr == 'e' || *curr == 'E') {
921   } else {
922     goto assemble;
923   }
924 
925   if (!end_not_reached) goto assemble;
926 
927   // Read the exponent part.
928   if (*curr == 'e' || *curr == 'E') {
929     curr++;
930     // Figure out if a sign is present and if it is.
931     end_not_reached = (curr != s_end);
932     if (end_not_reached && (*curr == '+' || *curr == '-')) {
933       exp_sign = *curr;
934       curr++;
935     } else if (IS_DIGIT(*curr)) { /* Pass through. */
936     } else {
937       // Empty E is not allowed.
938       goto fail;
939     }
940 
941     read = 0;
942     end_not_reached = (curr != s_end);
943     while (end_not_reached && IS_DIGIT(*curr)) {
944       exponent *= 10;
945       exponent += static_cast<int>(*curr - 0x30);
946       curr++;
947       read++;
948       end_not_reached = (curr != s_end);
949     }
950     exponent *= (exp_sign == '+' ? 1 : -1);
951     if (read == 0) goto fail;
952   }
953 
954 assemble:
955   *result = (sign == '+' ? 1 : -1) *
956             (exponent ? std::ldexp(mantissa * std::pow(5.0, exponent), exponent)
957                       : mantissa);
958   return true;
959 fail:
960   return false;
961 }
962 
963 static inline real_t parseReal(const char **token, double default_value = 0.0) {
964   (*token) += strspn((*token), " \t");
965   const char *end = (*token) + strcspn((*token), " \t\r");
966   double val = default_value;
967   tryParseDouble((*token), end, &val);
968   real_t f = static_cast<real_t>(val);
969   (*token) = end;
970   return f;
971 }
972 
parseReal(const char ** token,real_t * out)973 static inline bool parseReal(const char **token, real_t *out) {
974   (*token) += strspn((*token), " \t");
975   const char *end = (*token) + strcspn((*token), " \t\r");
976   double val;
977   bool ret = tryParseDouble((*token), end, &val);
978   if (ret) {
979     real_t f = static_cast<real_t>(val);
980     (*out) = f;
981   }
982   (*token) = end;
983   return ret;
984 }
985 
986 static inline void parseReal2(real_t *x, real_t *y, const char **token,
987                               const double default_x = 0.0,
988                               const double default_y = 0.0) {
989   (*x) = parseReal(token, default_x);
990   (*y) = parseReal(token, default_y);
991 }
992 
993 static inline void parseReal3(real_t *x, real_t *y, real_t *z,
994                               const char **token, const double default_x = 0.0,
995                               const double default_y = 0.0,
996                               const double default_z = 0.0) {
997   (*x) = parseReal(token, default_x);
998   (*y) = parseReal(token, default_y);
999   (*z) = parseReal(token, default_z);
1000 }
1001 
1002 static inline void parseV(real_t *x, real_t *y, real_t *z, real_t *w,
1003                           const char **token, const double default_x = 0.0,
1004                           const double default_y = 0.0,
1005                           const double default_z = 0.0,
1006                           const double default_w = 1.0) {
1007   (*x) = parseReal(token, default_x);
1008   (*y) = parseReal(token, default_y);
1009   (*z) = parseReal(token, default_z);
1010   (*w) = parseReal(token, default_w);
1011 }
1012 
1013 // Extension: parse vertex with colors(6 items)
1014 static inline bool parseVertexWithColor(real_t *x, real_t *y, real_t *z,
1015                                         real_t *r, real_t *g, real_t *b,
1016                                         const char **token,
1017                                         const double default_x = 0.0,
1018                                         const double default_y = 0.0,
1019                                         const double default_z = 0.0) {
1020   (*x) = parseReal(token, default_x);
1021   (*y) = parseReal(token, default_y);
1022   (*z) = parseReal(token, default_z);
1023 
1024   const bool found_color =
1025       parseReal(token, r) && parseReal(token, g) && parseReal(token, b);
1026 
1027   if (!found_color) {
1028     (*r) = (*g) = (*b) = 1.0;
1029   }
1030 
1031   return found_color;
1032 }
1033 
1034 static inline bool parseOnOff(const char **token, bool default_value = true) {
1035   (*token) += strspn((*token), " \t");
1036   const char *end = (*token) + strcspn((*token), " \t\r");
1037 
1038   bool ret = default_value;
1039   if ((0 == strncmp((*token), "on", 2))) {
1040     ret = true;
1041   } else if ((0 == strncmp((*token), "off", 3))) {
1042     ret = false;
1043   }
1044 
1045   (*token) = end;
1046   return ret;
1047 }
1048 
1049 static inline texture_type_t parseTextureType(
1050     const char **token, texture_type_t default_value = TEXTURE_TYPE_NONE) {
1051   (*token) += strspn((*token), " \t");
1052   const char *end = (*token) + strcspn((*token), " \t\r");
1053   texture_type_t ty = default_value;
1054 
1055   if ((0 == strncmp((*token), "cube_top", strlen("cube_top")))) {
1056     ty = TEXTURE_TYPE_CUBE_TOP;
1057   } else if ((0 == strncmp((*token), "cube_bottom", strlen("cube_bottom")))) {
1058     ty = TEXTURE_TYPE_CUBE_BOTTOM;
1059   } else if ((0 == strncmp((*token), "cube_left", strlen("cube_left")))) {
1060     ty = TEXTURE_TYPE_CUBE_LEFT;
1061   } else if ((0 == strncmp((*token), "cube_right", strlen("cube_right")))) {
1062     ty = TEXTURE_TYPE_CUBE_RIGHT;
1063   } else if ((0 == strncmp((*token), "cube_front", strlen("cube_front")))) {
1064     ty = TEXTURE_TYPE_CUBE_FRONT;
1065   } else if ((0 == strncmp((*token), "cube_back", strlen("cube_back")))) {
1066     ty = TEXTURE_TYPE_CUBE_BACK;
1067   } else if ((0 == strncmp((*token), "sphere", strlen("sphere")))) {
1068     ty = TEXTURE_TYPE_SPHERE;
1069   }
1070 
1071   (*token) = end;
1072   return ty;
1073 }
1074 
parseTagTriple(const char ** token)1075 static tag_sizes parseTagTriple(const char **token) {
1076   tag_sizes ts;
1077 
1078   (*token) += strspn((*token), " \t");
1079   ts.num_ints = atoi((*token));
1080   (*token) += strcspn((*token), "/ \t\r");
1081   if ((*token)[0] != '/') {
1082     return ts;
1083   }
1084 
1085   (*token)++;  // Skip '/'
1086 
1087   (*token) += strspn((*token), " \t");
1088   ts.num_reals = atoi((*token));
1089   (*token) += strcspn((*token), "/ \t\r");
1090   if ((*token)[0] != '/') {
1091     return ts;
1092   }
1093   (*token)++;  // Skip '/'
1094 
1095   ts.num_strings = parseInt(token);
1096 
1097   return ts;
1098 }
1099 
1100 // Parse triples with index offsets: i, i/j/k, i//k, i/j
parseTriple(const char ** token,int vsize,int vnsize,int vtsize,vertex_index_t * ret)1101 static bool parseTriple(const char **token, int vsize, int vnsize, int vtsize,
1102                         vertex_index_t *ret) {
1103   if (!ret) {
1104     return false;
1105   }
1106 
1107   vertex_index_t vi(-1);
1108 
1109   if (!fixIndex(atoi((*token)), vsize, &(vi.v_idx))) {
1110     return false;
1111   }
1112 
1113   (*token) += strcspn((*token), "/ \t\r");
1114   if ((*token)[0] != '/') {
1115     (*ret) = vi;
1116     return true;
1117   }
1118   (*token)++;
1119 
1120   // i//k
1121   if ((*token)[0] == '/') {
1122     (*token)++;
1123     if (!fixIndex(atoi((*token)), vnsize, &(vi.vn_idx))) {
1124       return false;
1125     }
1126     (*token) += strcspn((*token), "/ \t\r");
1127     (*ret) = vi;
1128     return true;
1129   }
1130 
1131   // i/j/k or i/j
1132   if (!fixIndex(atoi((*token)), vtsize, &(vi.vt_idx))) {
1133     return false;
1134   }
1135 
1136   (*token) += strcspn((*token), "/ \t\r");
1137   if ((*token)[0] != '/') {
1138     (*ret) = vi;
1139     return true;
1140   }
1141 
1142   // i/j/k
1143   (*token)++;  // skip '/'
1144   if (!fixIndex(atoi((*token)), vnsize, &(vi.vn_idx))) {
1145     return false;
1146   }
1147   (*token) += strcspn((*token), "/ \t\r");
1148 
1149   (*ret) = vi;
1150 
1151   return true;
1152 }
1153 
1154 // Parse raw triples: i, i/j/k, i//k, i/j
parseRawTriple(const char ** token)1155 static vertex_index_t parseRawTriple(const char **token) {
1156   vertex_index_t vi(static_cast<int>(0));  // 0 is an invalid index in OBJ
1157 
1158   vi.v_idx = atoi((*token));
1159   (*token) += strcspn((*token), "/ \t\r");
1160   if ((*token)[0] != '/') {
1161     return vi;
1162   }
1163   (*token)++;
1164 
1165   // i//k
1166   if ((*token)[0] == '/') {
1167     (*token)++;
1168     vi.vn_idx = atoi((*token));
1169     (*token) += strcspn((*token), "/ \t\r");
1170     return vi;
1171   }
1172 
1173   // i/j/k or i/j
1174   vi.vt_idx = atoi((*token));
1175   (*token) += strcspn((*token), "/ \t\r");
1176   if ((*token)[0] != '/') {
1177     return vi;
1178   }
1179 
1180   // i/j/k
1181   (*token)++;  // skip '/'
1182   vi.vn_idx = atoi((*token));
1183   (*token) += strcspn((*token), "/ \t\r");
1184   return vi;
1185 }
1186 
ParseTextureNameAndOption(std::string * texname,texture_option_t * texopt,const char * linebuf)1187 bool ParseTextureNameAndOption(std::string *texname, texture_option_t *texopt,
1188                                const char *linebuf) {
1189   // @todo { write more robust lexer and parser. }
1190   bool found_texname = false;
1191   std::string texture_name;
1192 
1193   const char *token = linebuf;  // Assume line ends with NULL
1194 
1195   while (!IS_NEW_LINE((*token))) {
1196     token += strspn(token, " \t");  // skip space
1197     if ((0 == strncmp(token, "-blendu", 7)) && IS_SPACE((token[7]))) {
1198       token += 8;
1199       texopt->blendu = parseOnOff(&token, /* default */ true);
1200     } else if ((0 == strncmp(token, "-blendv", 7)) && IS_SPACE((token[7]))) {
1201       token += 8;
1202       texopt->blendv = parseOnOff(&token, /* default */ true);
1203     } else if ((0 == strncmp(token, "-clamp", 6)) && IS_SPACE((token[6]))) {
1204       token += 7;
1205       texopt->clamp = parseOnOff(&token, /* default */ true);
1206     } else if ((0 == strncmp(token, "-boost", 6)) && IS_SPACE((token[6]))) {
1207       token += 7;
1208       texopt->sharpness = parseReal(&token, 1.0);
1209     } else if ((0 == strncmp(token, "-bm", 3)) && IS_SPACE((token[3]))) {
1210       token += 4;
1211       texopt->bump_multiplier = parseReal(&token, 1.0);
1212     } else if ((0 == strncmp(token, "-o", 2)) && IS_SPACE((token[2]))) {
1213       token += 3;
1214       parseReal3(&(texopt->origin_offset[0]), &(texopt->origin_offset[1]),
1215                  &(texopt->origin_offset[2]), &token);
1216     } else if ((0 == strncmp(token, "-s", 2)) && IS_SPACE((token[2]))) {
1217       token += 3;
1218       parseReal3(&(texopt->scale[0]), &(texopt->scale[1]), &(texopt->scale[2]),
1219                  &token, 1.0, 1.0, 1.0);
1220     } else if ((0 == strncmp(token, "-t", 2)) && IS_SPACE((token[2]))) {
1221       token += 3;
1222       parseReal3(&(texopt->turbulence[0]), &(texopt->turbulence[1]),
1223                  &(texopt->turbulence[2]), &token);
1224     } else if ((0 == strncmp(token, "-type", 5)) && IS_SPACE((token[5]))) {
1225       token += 5;
1226       texopt->type = parseTextureType((&token), TEXTURE_TYPE_NONE);
1227     } else if ((0 == strncmp(token, "-texres", 7)) && IS_SPACE((token[7]))) {
1228       token += 7;
1229       // TODO(syoyo): Check if arg is int type.
1230       texopt->texture_resolution = parseInt(&token);
1231     } else if ((0 == strncmp(token, "-imfchan", 8)) && IS_SPACE((token[8]))) {
1232       token += 9;
1233       token += strspn(token, " \t");
1234       const char *end = token + strcspn(token, " \t\r");
1235       if ((end - token) == 1) {  // Assume one char for -imfchan
1236         texopt->imfchan = (*token);
1237       }
1238       token = end;
1239     } else if ((0 == strncmp(token, "-mm", 3)) && IS_SPACE((token[3]))) {
1240       token += 4;
1241       parseReal2(&(texopt->brightness), &(texopt->contrast), &token, 0.0, 1.0);
1242     } else if ((0 == strncmp(token, "-colorspace", 11)) &&
1243                IS_SPACE((token[11]))) {
1244       token += 12;
1245       texopt->colorspace = parseString(&token);
1246     } else {
1247 // Assume texture filename
1248 #if 0
1249       size_t len = strcspn(token, " \t\r");  // untile next space
1250       texture_name = std::string(token, token + len);
1251       token += len;
1252 
1253       token += strspn(token, " \t");  // skip space
1254 #else
1255       // Read filename until line end to parse filename containing whitespace
1256       // TODO(syoyo): Support parsing texture option flag after the filename.
1257       texture_name = std::string(token);
1258       token += texture_name.length();
1259 #endif
1260 
1261       found_texname = true;
1262     }
1263   }
1264 
1265   if (found_texname) {
1266     (*texname) = texture_name;
1267     return true;
1268   } else {
1269     return false;
1270   }
1271 }
1272 
InitTexOpt(texture_option_t * texopt,const bool is_bump)1273 static void InitTexOpt(texture_option_t *texopt, const bool is_bump) {
1274   if (is_bump) {
1275     texopt->imfchan = 'l';
1276   } else {
1277     texopt->imfchan = 'm';
1278   }
1279   texopt->bump_multiplier = static_cast<real_t>(1.0);
1280   texopt->clamp = false;
1281   texopt->blendu = true;
1282   texopt->blendv = true;
1283   texopt->sharpness = static_cast<real_t>(1.0);
1284   texopt->brightness = static_cast<real_t>(0.0);
1285   texopt->contrast = static_cast<real_t>(1.0);
1286   texopt->origin_offset[0] = static_cast<real_t>(0.0);
1287   texopt->origin_offset[1] = static_cast<real_t>(0.0);
1288   texopt->origin_offset[2] = static_cast<real_t>(0.0);
1289   texopt->scale[0] = static_cast<real_t>(1.0);
1290   texopt->scale[1] = static_cast<real_t>(1.0);
1291   texopt->scale[2] = static_cast<real_t>(1.0);
1292   texopt->turbulence[0] = static_cast<real_t>(0.0);
1293   texopt->turbulence[1] = static_cast<real_t>(0.0);
1294   texopt->turbulence[2] = static_cast<real_t>(0.0);
1295   texopt->texture_resolution = -1;
1296   texopt->type = TEXTURE_TYPE_NONE;
1297 }
1298 
InitMaterial(material_t * material)1299 static void InitMaterial(material_t *material) {
1300   InitTexOpt(&material->ambient_texopt, /* is_bump */ false);
1301   InitTexOpt(&material->diffuse_texopt, /* is_bump */ false);
1302   InitTexOpt(&material->specular_texopt, /* is_bump */ false);
1303   InitTexOpt(&material->specular_highlight_texopt, /* is_bump */ false);
1304   InitTexOpt(&material->bump_texopt, /* is_bump */ true);
1305   InitTexOpt(&material->displacement_texopt, /* is_bump */ false);
1306   InitTexOpt(&material->alpha_texopt, /* is_bump */ false);
1307   InitTexOpt(&material->reflection_texopt, /* is_bump */ false);
1308   InitTexOpt(&material->roughness_texopt, /* is_bump */ false);
1309   InitTexOpt(&material->metallic_texopt, /* is_bump */ false);
1310   InitTexOpt(&material->sheen_texopt, /* is_bump */ false);
1311   InitTexOpt(&material->emissive_texopt, /* is_bump */ false);
1312   InitTexOpt(&material->normal_texopt,
1313              /* is_bump */ false);  // @fixme { is_bump will be true? }
1314   material->name = "";
1315   material->ambient_texname = "";
1316   material->diffuse_texname = "";
1317   material->specular_texname = "";
1318   material->specular_highlight_texname = "";
1319   material->bump_texname = "";
1320   material->displacement_texname = "";
1321   material->reflection_texname = "";
1322   material->alpha_texname = "";
1323   for (int i = 0; i < 3; i++) {
1324     material->ambient[i] = static_cast<real_t>(0.0);
1325     material->diffuse[i] = static_cast<real_t>(0.0);
1326     material->specular[i] = static_cast<real_t>(0.0);
1327     material->transmittance[i] = static_cast<real_t>(0.0);
1328     material->emission[i] = static_cast<real_t>(0.0);
1329   }
1330   material->illum = 0;
1331   material->dissolve = static_cast<real_t>(1.0);
1332   material->shininess = static_cast<real_t>(1.0);
1333   material->ior = static_cast<real_t>(1.0);
1334 
1335   material->roughness = static_cast<real_t>(0.0);
1336   material->metallic = static_cast<real_t>(0.0);
1337   material->sheen = static_cast<real_t>(0.0);
1338   material->clearcoat_thickness = static_cast<real_t>(0.0);
1339   material->clearcoat_roughness = static_cast<real_t>(0.0);
1340   material->anisotropy_rotation = static_cast<real_t>(0.0);
1341   material->anisotropy = static_cast<real_t>(0.0);
1342   material->roughness_texname = "";
1343   material->metallic_texname = "";
1344   material->sheen_texname = "";
1345   material->emissive_texname = "";
1346   material->normal_texname = "";
1347 
1348   material->unknown_parameter.clear();
1349 }
1350 
1351 // code from https://wrf.ecse.rpi.edu//Research/Short_Notes/pnpoly.html
1352 template <typename T>
pnpoly(int nvert,T * vertx,T * verty,T testx,T testy)1353 static int pnpoly(int nvert, T *vertx, T *verty, T testx, T testy) {
1354   int i, j, c = 0;
1355   for (i = 0, j = nvert - 1; i < nvert; j = i++) {
1356     if (((verty[i] > testy) != (verty[j] > testy)) &&
1357         (testx <
1358          (vertx[j] - vertx[i]) * (testy - verty[i]) / (verty[j] - verty[i]) +
1359              vertx[i]))
1360       c = !c;
1361   }
1362   return c;
1363 }
1364 
1365 // TODO(syoyo): refactor function.
exportGroupsToShape(shape_t * shape,const PrimGroup & prim_group,const std::vector<tag_t> & tags,const int material_id,const std::string & name,bool triangulate,const std::vector<real_t> & v,std::string * warn)1366 static bool exportGroupsToShape(shape_t *shape, const PrimGroup &prim_group,
1367                                 const std::vector<tag_t> &tags,
1368                                 const int material_id, const std::string &name,
1369                                 bool triangulate, const std::vector<real_t> &v,
1370                                 std::string *warn) {
1371   if (prim_group.IsEmpty()) {
1372     return false;
1373   }
1374 
1375   shape->name = name;
1376 
1377   // polygon
1378   if (!prim_group.faceGroup.empty()) {
1379     // Flatten vertices and indices
1380     for (size_t i = 0; i < prim_group.faceGroup.size(); i++) {
1381       const face_t &face = prim_group.faceGroup[i];
1382 
1383       size_t npolys = face.vertex_indices.size();
1384 
1385       if (npolys < 3) {
1386         // Face must have 3+ vertices.
1387         if (warn) {
1388           (*warn) += "Degenerated face found\n.";
1389         }
1390         continue;
1391       }
1392 
1393       if (triangulate) {
1394         if (npolys == 4) {
1395           vertex_index_t i0 = face.vertex_indices[0];
1396           vertex_index_t i1 = face.vertex_indices[1];
1397           vertex_index_t i2 = face.vertex_indices[2];
1398           vertex_index_t i3 = face.vertex_indices[3];
1399 
1400           size_t vi0 = size_t(i0.v_idx);
1401           size_t vi1 = size_t(i1.v_idx);
1402           size_t vi2 = size_t(i2.v_idx);
1403           size_t vi3 = size_t(i3.v_idx);
1404 
1405           if (((3 * vi0 + 2) >= v.size()) || ((3 * vi1 + 2) >= v.size()) ||
1406               ((3 * vi2 + 2) >= v.size()) || ((3 * vi3 + 2) >= v.size())) {
1407             // Invalid triangle.
1408             // FIXME(syoyo): Is it ok to simply skip this invalid triangle?
1409             if (warn) {
1410               (*warn) += "Face with invalid vertex index found.\n";
1411             }
1412             continue;
1413           }
1414 
1415           real_t v0x = v[vi0 * 3 + 0];
1416           real_t v0y = v[vi0 * 3 + 1];
1417           real_t v0z = v[vi0 * 3 + 2];
1418           real_t v1x = v[vi1 * 3 + 0];
1419           real_t v1y = v[vi1 * 3 + 1];
1420           real_t v1z = v[vi1 * 3 + 2];
1421           real_t v2x = v[vi2 * 3 + 0];
1422           real_t v2y = v[vi2 * 3 + 1];
1423           real_t v2z = v[vi2 * 3 + 2];
1424           real_t v3x = v[vi3 * 3 + 0];
1425           real_t v3y = v[vi3 * 3 + 1];
1426           real_t v3z = v[vi3 * 3 + 2];
1427 
1428           // There are two candidates to split the quad into two triangles.
1429           //
1430           // Choose the shortest edge.
1431           // TODO: Is it better to determine the edge to split by calculating
1432           // the area of each triangle?
1433           //
1434           // +---+
1435           // |\  |
1436           // | \ |
1437           // |  \|
1438           // +---+
1439           //
1440           // +---+
1441           // |  /|
1442           // | / |
1443           // |/  |
1444           // +---+
1445 
1446           real_t e02x = v2x - v0x;
1447           real_t e02y = v2y - v0y;
1448           real_t e02z = v2z - v0z;
1449           real_t e13x = v3x - v1x;
1450           real_t e13y = v3y - v1y;
1451           real_t e13z = v3z - v1z;
1452 
1453           real_t sqr02 = e02x * e02x + e02y * e02y + e02z * e02z;
1454           real_t sqr13 = e13x * e13x + e13y * e13y + e13z * e13z;
1455 
1456           index_t idx0, idx1, idx2, idx3;
1457 
1458           idx0.vertex_index = i0.v_idx;
1459           idx0.normal_index = i0.vn_idx;
1460           idx0.texcoord_index = i0.vt_idx;
1461           idx1.vertex_index = i1.v_idx;
1462           idx1.normal_index = i1.vn_idx;
1463           idx1.texcoord_index = i1.vt_idx;
1464           idx2.vertex_index = i2.v_idx;
1465           idx2.normal_index = i2.vn_idx;
1466           idx2.texcoord_index = i2.vt_idx;
1467           idx3.vertex_index = i3.v_idx;
1468           idx3.normal_index = i3.vn_idx;
1469           idx3.texcoord_index = i3.vt_idx;
1470 
1471           if (sqr02 < sqr13) {
1472             // [0, 1, 2], [0, 2, 3]
1473             shape->mesh.indices.push_back(idx0);
1474             shape->mesh.indices.push_back(idx1);
1475             shape->mesh.indices.push_back(idx2);
1476 
1477             shape->mesh.indices.push_back(idx0);
1478             shape->mesh.indices.push_back(idx2);
1479             shape->mesh.indices.push_back(idx3);
1480           } else {
1481             // [0, 1, 3], [1, 2, 3]
1482             shape->mesh.indices.push_back(idx0);
1483             shape->mesh.indices.push_back(idx1);
1484             shape->mesh.indices.push_back(idx3);
1485 
1486             shape->mesh.indices.push_back(idx1);
1487             shape->mesh.indices.push_back(idx2);
1488             shape->mesh.indices.push_back(idx3);
1489           }
1490 
1491           // Two triangle faces
1492           shape->mesh.num_face_vertices.push_back(3);
1493           shape->mesh.num_face_vertices.push_back(3);
1494 
1495           shape->mesh.material_ids.push_back(material_id);
1496           shape->mesh.material_ids.push_back(material_id);
1497 
1498           shape->mesh.smoothing_group_ids.push_back(face.smoothing_group_id);
1499           shape->mesh.smoothing_group_ids.push_back(face.smoothing_group_id);
1500 
1501         } else {
1502           vertex_index_t i0 = face.vertex_indices[0];
1503           vertex_index_t i1(-1);
1504           vertex_index_t i2 = face.vertex_indices[1];
1505 
1506           // find the two axes to work in
1507           size_t axes[2] = {1, 2};
1508           for (size_t k = 0; k < npolys; ++k) {
1509             i0 = face.vertex_indices[(k + 0) % npolys];
1510             i1 = face.vertex_indices[(k + 1) % npolys];
1511             i2 = face.vertex_indices[(k + 2) % npolys];
1512             size_t vi0 = size_t(i0.v_idx);
1513             size_t vi1 = size_t(i1.v_idx);
1514             size_t vi2 = size_t(i2.v_idx);
1515 
1516             if (((3 * vi0 + 2) >= v.size()) || ((3 * vi1 + 2) >= v.size()) ||
1517                 ((3 * vi2 + 2) >= v.size())) {
1518               // Invalid triangle.
1519               // FIXME(syoyo): Is it ok to simply skip this invalid triangle?
1520               continue;
1521             }
1522             real_t v0x = v[vi0 * 3 + 0];
1523             real_t v0y = v[vi0 * 3 + 1];
1524             real_t v0z = v[vi0 * 3 + 2];
1525             real_t v1x = v[vi1 * 3 + 0];
1526             real_t v1y = v[vi1 * 3 + 1];
1527             real_t v1z = v[vi1 * 3 + 2];
1528             real_t v2x = v[vi2 * 3 + 0];
1529             real_t v2y = v[vi2 * 3 + 1];
1530             real_t v2z = v[vi2 * 3 + 2];
1531             real_t e0x = v1x - v0x;
1532             real_t e0y = v1y - v0y;
1533             real_t e0z = v1z - v0z;
1534             real_t e1x = v2x - v1x;
1535             real_t e1y = v2y - v1y;
1536             real_t e1z = v2z - v1z;
1537             real_t cx = std::fabs(e0y * e1z - e0z * e1y);
1538             real_t cy = std::fabs(e0z * e1x - e0x * e1z);
1539             real_t cz = std::fabs(e0x * e1y - e0y * e1x);
1540             const real_t epsilon = std::numeric_limits<real_t>::epsilon();
1541             if (cx > epsilon || cy > epsilon || cz > epsilon) {
1542               // found a corner
1543               if (cx > cy && cx > cz) {
1544               } else {
1545                 axes[0] = 0;
1546                 if (cz > cx && cz > cy) axes[1] = 1;
1547               }
1548               break;
1549             }
1550           }
1551 
1552           real_t area = 0;
1553           for (size_t k = 0; k < npolys; ++k) {
1554             i0 = face.vertex_indices[(k + 0) % npolys];
1555             i1 = face.vertex_indices[(k + 1) % npolys];
1556             size_t vi0 = size_t(i0.v_idx);
1557             size_t vi1 = size_t(i1.v_idx);
1558             if (((vi0 * 3 + axes[0]) >= v.size()) ||
1559                 ((vi0 * 3 + axes[1]) >= v.size()) ||
1560                 ((vi1 * 3 + axes[0]) >= v.size()) ||
1561                 ((vi1 * 3 + axes[1]) >= v.size())) {
1562               // Invalid index.
1563               continue;
1564             }
1565             real_t v0x = v[vi0 * 3 + axes[0]];
1566             real_t v0y = v[vi0 * 3 + axes[1]];
1567             real_t v1x = v[vi1 * 3 + axes[0]];
1568             real_t v1y = v[vi1 * 3 + axes[1]];
1569             area += (v0x * v1y - v0y * v1x) * static_cast<real_t>(0.5);
1570           }
1571 
1572           face_t remainingFace = face;  // copy
1573           size_t guess_vert = 0;
1574           vertex_index_t ind[3];
1575           real_t vx[3];
1576           real_t vy[3];
1577 
1578           // How many iterations can we do without decreasing the remaining
1579           // vertices.
1580           size_t remainingIterations = face.vertex_indices.size();
1581           size_t previousRemainingVertices =
1582               remainingFace.vertex_indices.size();
1583 
1584           while (remainingFace.vertex_indices.size() > 3 &&
1585                  remainingIterations > 0) {
1586             npolys = remainingFace.vertex_indices.size();
1587             if (guess_vert >= npolys) {
1588               guess_vert -= npolys;
1589             }
1590 
1591             if (previousRemainingVertices != npolys) {
1592               // The number of remaining vertices decreased. Reset counters.
1593               previousRemainingVertices = npolys;
1594               remainingIterations = npolys;
1595             } else {
1596               // We didn't consume a vertex on previous iteration, reduce the
1597               // available iterations.
1598               remainingIterations--;
1599             }
1600 
1601             for (size_t k = 0; k < 3; k++) {
1602               ind[k] = remainingFace.vertex_indices[(guess_vert + k) % npolys];
1603               size_t vi = size_t(ind[k].v_idx);
1604               if (((vi * 3 + axes[0]) >= v.size()) ||
1605                   ((vi * 3 + axes[1]) >= v.size())) {
1606                 // ???
1607                 vx[k] = static_cast<real_t>(0.0);
1608                 vy[k] = static_cast<real_t>(0.0);
1609               } else {
1610                 vx[k] = v[vi * 3 + axes[0]];
1611                 vy[k] = v[vi * 3 + axes[1]];
1612               }
1613             }
1614             real_t e0x = vx[1] - vx[0];
1615             real_t e0y = vy[1] - vy[0];
1616             real_t e1x = vx[2] - vx[1];
1617             real_t e1y = vy[2] - vy[1];
1618             real_t cross = e0x * e1y - e0y * e1x;
1619             // if an internal angle
1620             if (cross * area < static_cast<real_t>(0.0)) {
1621               guess_vert += 1;
1622               continue;
1623             }
1624 
1625             // check all other verts in case they are inside this triangle
1626             bool overlap = false;
1627             for (size_t otherVert = 3; otherVert < npolys; ++otherVert) {
1628               size_t idx = (guess_vert + otherVert) % npolys;
1629 
1630               if (idx >= remainingFace.vertex_indices.size()) {
1631                 // ???
1632                 continue;
1633               }
1634 
1635               size_t ovi = size_t(remainingFace.vertex_indices[idx].v_idx);
1636 
1637               if (((ovi * 3 + axes[0]) >= v.size()) ||
1638                   ((ovi * 3 + axes[1]) >= v.size())) {
1639                 // ???
1640                 continue;
1641               }
1642               real_t tx = v[ovi * 3 + axes[0]];
1643               real_t ty = v[ovi * 3 + axes[1]];
1644               if (pnpoly(3, vx, vy, tx, ty)) {
1645                 overlap = true;
1646                 break;
1647               }
1648             }
1649 
1650             if (overlap) {
1651               guess_vert += 1;
1652               continue;
1653             }
1654 
1655             // this triangle is an ear
1656             {
1657               index_t idx0, idx1, idx2;
1658               idx0.vertex_index = ind[0].v_idx;
1659               idx0.normal_index = ind[0].vn_idx;
1660               idx0.texcoord_index = ind[0].vt_idx;
1661               idx1.vertex_index = ind[1].v_idx;
1662               idx1.normal_index = ind[1].vn_idx;
1663               idx1.texcoord_index = ind[1].vt_idx;
1664               idx2.vertex_index = ind[2].v_idx;
1665               idx2.normal_index = ind[2].vn_idx;
1666               idx2.texcoord_index = ind[2].vt_idx;
1667 
1668               shape->mesh.indices.push_back(idx0);
1669               shape->mesh.indices.push_back(idx1);
1670               shape->mesh.indices.push_back(idx2);
1671 
1672               shape->mesh.num_face_vertices.push_back(3);
1673               shape->mesh.material_ids.push_back(material_id);
1674               shape->mesh.smoothing_group_ids.push_back(
1675                   face.smoothing_group_id);
1676             }
1677 
1678             // remove v1 from the list
1679             size_t removed_vert_index = (guess_vert + 1) % npolys;
1680             while (removed_vert_index + 1 < npolys) {
1681               remainingFace.vertex_indices[removed_vert_index] =
1682                   remainingFace.vertex_indices[removed_vert_index + 1];
1683               removed_vert_index += 1;
1684             }
1685             remainingFace.vertex_indices.pop_back();
1686           }
1687 
1688           if (remainingFace.vertex_indices.size() == 3) {
1689             i0 = remainingFace.vertex_indices[0];
1690             i1 = remainingFace.vertex_indices[1];
1691             i2 = remainingFace.vertex_indices[2];
1692             {
1693               index_t idx0, idx1, idx2;
1694               idx0.vertex_index = i0.v_idx;
1695               idx0.normal_index = i0.vn_idx;
1696               idx0.texcoord_index = i0.vt_idx;
1697               idx1.vertex_index = i1.v_idx;
1698               idx1.normal_index = i1.vn_idx;
1699               idx1.texcoord_index = i1.vt_idx;
1700               idx2.vertex_index = i2.v_idx;
1701               idx2.normal_index = i2.vn_idx;
1702               idx2.texcoord_index = i2.vt_idx;
1703 
1704               shape->mesh.indices.push_back(idx0);
1705               shape->mesh.indices.push_back(idx1);
1706               shape->mesh.indices.push_back(idx2);
1707 
1708               shape->mesh.num_face_vertices.push_back(3);
1709               shape->mesh.material_ids.push_back(material_id);
1710               shape->mesh.smoothing_group_ids.push_back(
1711                   face.smoothing_group_id);
1712             }
1713           }
1714         }  // npolys
1715       } else {
1716         for (size_t k = 0; k < npolys; k++) {
1717           index_t idx;
1718           idx.vertex_index = face.vertex_indices[k].v_idx;
1719           idx.normal_index = face.vertex_indices[k].vn_idx;
1720           idx.texcoord_index = face.vertex_indices[k].vt_idx;
1721           shape->mesh.indices.push_back(idx);
1722         }
1723 
1724         shape->mesh.num_face_vertices.push_back(
1725             static_cast<unsigned char>(npolys));
1726         shape->mesh.material_ids.push_back(material_id);  // per face
1727         shape->mesh.smoothing_group_ids.push_back(
1728             face.smoothing_group_id);  // per face
1729       }
1730     }
1731 
1732     shape->mesh.tags = tags;
1733   }
1734 
1735   // line
1736   if (!prim_group.lineGroup.empty()) {
1737     // Flatten indices
1738     for (size_t i = 0; i < prim_group.lineGroup.size(); i++) {
1739       for (size_t j = 0; j < prim_group.lineGroup[i].vertex_indices.size();
1740            j++) {
1741         const vertex_index_t &vi = prim_group.lineGroup[i].vertex_indices[j];
1742 
1743         index_t idx;
1744         idx.vertex_index = vi.v_idx;
1745         idx.normal_index = vi.vn_idx;
1746         idx.texcoord_index = vi.vt_idx;
1747 
1748         shape->lines.indices.push_back(idx);
1749       }
1750 
1751       shape->lines.num_line_vertices.push_back(
1752           int(prim_group.lineGroup[i].vertex_indices.size()));
1753     }
1754   }
1755 
1756   // points
1757   if (!prim_group.pointsGroup.empty()) {
1758     // Flatten & convert indices
1759     for (size_t i = 0; i < prim_group.pointsGroup.size(); i++) {
1760       for (size_t j = 0; j < prim_group.pointsGroup[i].vertex_indices.size();
1761            j++) {
1762         const vertex_index_t &vi = prim_group.pointsGroup[i].vertex_indices[j];
1763 
1764         index_t idx;
1765         idx.vertex_index = vi.v_idx;
1766         idx.normal_index = vi.vn_idx;
1767         idx.texcoord_index = vi.vt_idx;
1768 
1769         shape->points.indices.push_back(idx);
1770       }
1771     }
1772   }
1773 
1774   return true;
1775 }
1776 
1777 // Split a string with specified delimiter character and escape character.
1778 // https://rosettacode.org/wiki/Tokenize_a_string_with_escaping#C.2B.2B
SplitString(const std::string & s,char delim,char escape,std::vector<std::string> & elems)1779 static void SplitString(const std::string &s, char delim, char escape,
1780                         std::vector<std::string> &elems) {
1781   std::string token;
1782 
1783   bool escaping = false;
1784   for (size_t i = 0; i < s.size(); ++i) {
1785     char ch = s[i];
1786     if (escaping) {
1787       escaping = false;
1788     } else if (ch == escape) {
1789       escaping = true;
1790       continue;
1791     } else if (ch == delim) {
1792       if (!token.empty()) {
1793         elems.push_back(token);
1794       }
1795       token.clear();
1796       continue;
1797     }
1798     token += ch;
1799   }
1800 
1801   elems.push_back(token);
1802 }
1803 
JoinPath(const std::string & dir,const std::string & filename)1804 static std::string JoinPath(const std::string &dir,
1805                             const std::string &filename) {
1806   if (dir.empty()) {
1807     return filename;
1808   } else {
1809     // check '/'
1810     char lastChar = *dir.rbegin();
1811     if (lastChar != '/') {
1812       return dir + std::string("/") + filename;
1813     } else {
1814       return dir + filename;
1815     }
1816   }
1817 }
1818 
LoadMtl(std::map<std::string,int> * material_map,std::vector<material_t> * materials,std::istream * inStream,std::string * warning,std::string * err)1819 void LoadMtl(std::map<std::string, int> *material_map,
1820              std::vector<material_t> *materials, std::istream *inStream,
1821              std::string *warning, std::string *err) {
1822   (void)err;
1823 
1824   // Create a default material anyway.
1825   material_t material;
1826   InitMaterial(&material);
1827 
1828   // Issue 43. `d` wins against `Tr` since `Tr` is not in the MTL specification.
1829   bool has_d = false;
1830   bool has_tr = false;
1831 
1832   // has_kd is used to set a default diffuse value when map_Kd is present
1833   // and Kd is not.
1834   bool has_kd = false;
1835 
1836   std::stringstream warn_ss;
1837 
1838   size_t line_no = 0;
1839   std::string linebuf;
1840   while (inStream->peek() != -1) {
1841     safeGetline(*inStream, linebuf);
1842     line_no++;
1843 
1844     // Trim trailing whitespace.
1845     if (linebuf.size() > 0) {
1846       linebuf = linebuf.substr(0, linebuf.find_last_not_of(" \t") + 1);
1847     }
1848 
1849     // Trim newline '\r\n' or '\n'
1850     if (linebuf.size() > 0) {
1851       if (linebuf[linebuf.size() - 1] == '\n')
1852         linebuf.erase(linebuf.size() - 1);
1853     }
1854     if (linebuf.size() > 0) {
1855       if (linebuf[linebuf.size() - 1] == '\r')
1856         linebuf.erase(linebuf.size() - 1);
1857     }
1858 
1859     // Skip if empty line.
1860     if (linebuf.empty()) {
1861       continue;
1862     }
1863 
1864     // Skip leading space.
1865     const char *token = linebuf.c_str();
1866     token += strspn(token, " \t");
1867 
1868     assert(token);
1869     if (token[0] == '\0') continue;  // empty line
1870 
1871     if (token[0] == '#') continue;  // comment line
1872 
1873     // new mtl
1874     if ((0 == strncmp(token, "newmtl", 6)) && IS_SPACE((token[6]))) {
1875       // flush previous material.
1876       if (!material.name.empty()) {
1877         material_map->insert(std::pair<std::string, int>(
1878             material.name, static_cast<int>(materials->size())));
1879         materials->push_back(material);
1880       }
1881 
1882       // initial temporary material
1883       InitMaterial(&material);
1884 
1885       has_d = false;
1886       has_tr = false;
1887 
1888       // set new mtl name
1889       token += 7;
1890       {
1891         std::stringstream sstr;
1892         sstr << token;
1893         material.name = sstr.str();
1894       }
1895       continue;
1896     }
1897 
1898     // ambient
1899     if (token[0] == 'K' && token[1] == 'a' && IS_SPACE((token[2]))) {
1900       token += 2;
1901       real_t r, g, b;
1902       parseReal3(&r, &g, &b, &token);
1903       material.ambient[0] = r;
1904       material.ambient[1] = g;
1905       material.ambient[2] = b;
1906       continue;
1907     }
1908 
1909     // diffuse
1910     if (token[0] == 'K' && token[1] == 'd' && IS_SPACE((token[2]))) {
1911       token += 2;
1912       real_t r, g, b;
1913       parseReal3(&r, &g, &b, &token);
1914       material.diffuse[0] = r;
1915       material.diffuse[1] = g;
1916       material.diffuse[2] = b;
1917       has_kd = true;
1918       continue;
1919     }
1920 
1921     // specular
1922     if (token[0] == 'K' && token[1] == 's' && IS_SPACE((token[2]))) {
1923       token += 2;
1924       real_t r, g, b;
1925       parseReal3(&r, &g, &b, &token);
1926       material.specular[0] = r;
1927       material.specular[1] = g;
1928       material.specular[2] = b;
1929       continue;
1930     }
1931 
1932     // transmittance
1933     if ((token[0] == 'K' && token[1] == 't' && IS_SPACE((token[2]))) ||
1934         (token[0] == 'T' && token[1] == 'f' && IS_SPACE((token[2])))) {
1935       token += 2;
1936       real_t r, g, b;
1937       parseReal3(&r, &g, &b, &token);
1938       material.transmittance[0] = r;
1939       material.transmittance[1] = g;
1940       material.transmittance[2] = b;
1941       continue;
1942     }
1943 
1944     // ior(index of refraction)
1945     if (token[0] == 'N' && token[1] == 'i' && IS_SPACE((token[2]))) {
1946       token += 2;
1947       material.ior = parseReal(&token);
1948       continue;
1949     }
1950 
1951     // emission
1952     if (token[0] == 'K' && token[1] == 'e' && IS_SPACE(token[2])) {
1953       token += 2;
1954       real_t r, g, b;
1955       parseReal3(&r, &g, &b, &token);
1956       material.emission[0] = r;
1957       material.emission[1] = g;
1958       material.emission[2] = b;
1959       continue;
1960     }
1961 
1962     // shininess
1963     if (token[0] == 'N' && token[1] == 's' && IS_SPACE(token[2])) {
1964       token += 2;
1965       material.shininess = parseReal(&token);
1966       continue;
1967     }
1968 
1969     // illum model
1970     if (0 == strncmp(token, "illum", 5) && IS_SPACE(token[5])) {
1971       token += 6;
1972       material.illum = parseInt(&token);
1973       continue;
1974     }
1975 
1976     // dissolve
1977     if ((token[0] == 'd' && IS_SPACE(token[1]))) {
1978       token += 1;
1979       material.dissolve = parseReal(&token);
1980 
1981       if (has_tr) {
1982         warn_ss << "Both `d` and `Tr` parameters defined for \""
1983                 << material.name
1984                 << "\". Use the value of `d` for dissolve (line " << line_no
1985                 << " in .mtl.)" << std::endl;
1986       }
1987       has_d = true;
1988       continue;
1989     }
1990     if (token[0] == 'T' && token[1] == 'r' && IS_SPACE(token[2])) {
1991       token += 2;
1992       if (has_d) {
1993         // `d` wins. Ignore `Tr` value.
1994         warn_ss << "Both `d` and `Tr` parameters defined for \""
1995                 << material.name
1996                 << "\". Use the value of `d` for dissolve (line " << line_no
1997                 << " in .mtl.)" << std::endl;
1998       } else {
1999         // We invert value of Tr(assume Tr is in range [0, 1])
2000         // NOTE: Interpretation of Tr is application(exporter) dependent. For
2001         // some application(e.g. 3ds max obj exporter), Tr = d(Issue 43)
2002         material.dissolve = static_cast<real_t>(1.0) - parseReal(&token);
2003       }
2004       has_tr = true;
2005       continue;
2006     }
2007 
2008     // PBR: roughness
2009     if (token[0] == 'P' && token[1] == 'r' && IS_SPACE(token[2])) {
2010       token += 2;
2011       material.roughness = parseReal(&token);
2012       continue;
2013     }
2014 
2015     // PBR: metallic
2016     if (token[0] == 'P' && token[1] == 'm' && IS_SPACE(token[2])) {
2017       token += 2;
2018       material.metallic = parseReal(&token);
2019       continue;
2020     }
2021 
2022     // PBR: sheen
2023     if (token[0] == 'P' && token[1] == 's' && IS_SPACE(token[2])) {
2024       token += 2;
2025       material.sheen = parseReal(&token);
2026       continue;
2027     }
2028 
2029     // PBR: clearcoat thickness
2030     if (token[0] == 'P' && token[1] == 'c' && IS_SPACE(token[2])) {
2031       token += 2;
2032       material.clearcoat_thickness = parseReal(&token);
2033       continue;
2034     }
2035 
2036     // PBR: clearcoat roughness
2037     if ((0 == strncmp(token, "Pcr", 3)) && IS_SPACE(token[3])) {
2038       token += 4;
2039       material.clearcoat_roughness = parseReal(&token);
2040       continue;
2041     }
2042 
2043     // PBR: anisotropy
2044     if ((0 == strncmp(token, "aniso", 5)) && IS_SPACE(token[5])) {
2045       token += 6;
2046       material.anisotropy = parseReal(&token);
2047       continue;
2048     }
2049 
2050     // PBR: anisotropy rotation
2051     if ((0 == strncmp(token, "anisor", 6)) && IS_SPACE(token[6])) {
2052       token += 7;
2053       material.anisotropy_rotation = parseReal(&token);
2054       continue;
2055     }
2056 
2057     // ambient texture
2058     if ((0 == strncmp(token, "map_Ka", 6)) && IS_SPACE(token[6])) {
2059       token += 7;
2060       ParseTextureNameAndOption(&(material.ambient_texname),
2061                                 &(material.ambient_texopt), token);
2062       continue;
2063     }
2064 
2065     // diffuse texture
2066     if ((0 == strncmp(token, "map_Kd", 6)) && IS_SPACE(token[6])) {
2067       token += 7;
2068       ParseTextureNameAndOption(&(material.diffuse_texname),
2069                                 &(material.diffuse_texopt), token);
2070 
2071       // Set a decent diffuse default value if a diffuse texture is specified
2072       // without a matching Kd value.
2073       if (!has_kd) {
2074         material.diffuse[0] = static_cast<real_t>(0.6);
2075         material.diffuse[1] = static_cast<real_t>(0.6);
2076         material.diffuse[2] = static_cast<real_t>(0.6);
2077       }
2078 
2079       continue;
2080     }
2081 
2082     // specular texture
2083     if ((0 == strncmp(token, "map_Ks", 6)) && IS_SPACE(token[6])) {
2084       token += 7;
2085       ParseTextureNameAndOption(&(material.specular_texname),
2086                                 &(material.specular_texopt), token);
2087       continue;
2088     }
2089 
2090     // specular highlight texture
2091     if ((0 == strncmp(token, "map_Ns", 6)) && IS_SPACE(token[6])) {
2092       token += 7;
2093       ParseTextureNameAndOption(&(material.specular_highlight_texname),
2094                                 &(material.specular_highlight_texopt), token);
2095       continue;
2096     }
2097 
2098     // bump texture
2099     if ((0 == strncmp(token, "map_bump", 8)) && IS_SPACE(token[8])) {
2100       token += 9;
2101       ParseTextureNameAndOption(&(material.bump_texname),
2102                                 &(material.bump_texopt), token);
2103       continue;
2104     }
2105 
2106     // bump texture
2107     if ((0 == strncmp(token, "map_Bump", 8)) && IS_SPACE(token[8])) {
2108       token += 9;
2109       ParseTextureNameAndOption(&(material.bump_texname),
2110                                 &(material.bump_texopt), token);
2111       continue;
2112     }
2113 
2114     // bump texture
2115     if ((0 == strncmp(token, "bump", 4)) && IS_SPACE(token[4])) {
2116       token += 5;
2117       ParseTextureNameAndOption(&(material.bump_texname),
2118                                 &(material.bump_texopt), token);
2119       continue;
2120     }
2121 
2122     // alpha texture
2123     if ((0 == strncmp(token, "map_d", 5)) && IS_SPACE(token[5])) {
2124       token += 6;
2125       material.alpha_texname = token;
2126       ParseTextureNameAndOption(&(material.alpha_texname),
2127                                 &(material.alpha_texopt), token);
2128       continue;
2129     }
2130 
2131     // displacement texture
2132     if ((0 == strncmp(token, "disp", 4)) && IS_SPACE(token[4])) {
2133       token += 5;
2134       ParseTextureNameAndOption(&(material.displacement_texname),
2135                                 &(material.displacement_texopt), token);
2136       continue;
2137     }
2138 
2139     // reflection map
2140     if ((0 == strncmp(token, "refl", 4)) && IS_SPACE(token[4])) {
2141       token += 5;
2142       ParseTextureNameAndOption(&(material.reflection_texname),
2143                                 &(material.reflection_texopt), token);
2144       continue;
2145     }
2146 
2147     // PBR: roughness texture
2148     if ((0 == strncmp(token, "map_Pr", 6)) && IS_SPACE(token[6])) {
2149       token += 7;
2150       ParseTextureNameAndOption(&(material.roughness_texname),
2151                                 &(material.roughness_texopt), token);
2152       continue;
2153     }
2154 
2155     // PBR: metallic texture
2156     if ((0 == strncmp(token, "map_Pm", 6)) && IS_SPACE(token[6])) {
2157       token += 7;
2158       ParseTextureNameAndOption(&(material.metallic_texname),
2159                                 &(material.metallic_texopt), token);
2160       continue;
2161     }
2162 
2163     // PBR: sheen texture
2164     if ((0 == strncmp(token, "map_Ps", 6)) && IS_SPACE(token[6])) {
2165       token += 7;
2166       ParseTextureNameAndOption(&(material.sheen_texname),
2167                                 &(material.sheen_texopt), token);
2168       continue;
2169     }
2170 
2171     // PBR: emissive texture
2172     if ((0 == strncmp(token, "map_Ke", 6)) && IS_SPACE(token[6])) {
2173       token += 7;
2174       ParseTextureNameAndOption(&(material.emissive_texname),
2175                                 &(material.emissive_texopt), token);
2176       continue;
2177     }
2178 
2179     // PBR: normal map texture
2180     if ((0 == strncmp(token, "norm", 4)) && IS_SPACE(token[4])) {
2181       token += 5;
2182       ParseTextureNameAndOption(&(material.normal_texname),
2183                                 &(material.normal_texopt), token);
2184       continue;
2185     }
2186 
2187     // unknown parameter
2188     const char *_space = strchr(token, ' ');
2189     if (!_space) {
2190       _space = strchr(token, '\t');
2191     }
2192     if (_space) {
2193       std::ptrdiff_t len = _space - token;
2194       std::string key(token, static_cast<size_t>(len));
2195       std::string value = _space + 1;
2196       material.unknown_parameter.insert(
2197           std::pair<std::string, std::string>(key, value));
2198     }
2199   }
2200   // flush last material.
2201   material_map->insert(std::pair<std::string, int>(
2202       material.name, static_cast<int>(materials->size())));
2203   materials->push_back(material);
2204 
2205   if (warning) {
2206     (*warning) = warn_ss.str();
2207   }
2208 }
2209 
operator()2210 bool MaterialFileReader::operator()(const std::string &matId,
2211                                     std::vector<material_t> *materials,
2212                                     std::map<std::string, int> *matMap,
2213                                     std::string *warn, std::string *err) {
2214   if (!m_mtlBaseDir.empty()) {
2215 #ifdef _WIN32
2216     char sep = ';';
2217 #else
2218     char sep = ':';
2219 #endif
2220 
2221     // https://stackoverflow.com/questions/5167625/splitting-a-c-stdstring-using-tokens-e-g
2222     std::vector<std::string> paths;
2223     std::istringstream f(m_mtlBaseDir);
2224 
2225     std::string s;
2226     while (getline(f, s, sep)) {
2227       paths.push_back(s);
2228     }
2229 
2230     for (size_t i = 0; i < paths.size(); i++) {
2231       std::string filepath = JoinPath(paths[i], matId);
2232 
2233       std::ifstream matIStream(filepath.c_str());
2234       if (matIStream) {
2235         LoadMtl(matMap, materials, &matIStream, warn, err);
2236 
2237         return true;
2238       }
2239     }
2240 
2241     std::stringstream ss;
2242     ss << "Material file [ " << matId
2243        << " ] not found in a path : " << m_mtlBaseDir << std::endl;
2244     if (warn) {
2245       (*warn) += ss.str();
2246     }
2247     return false;
2248 
2249   } else {
2250     std::string filepath = matId;
2251     std::ifstream matIStream(filepath.c_str());
2252     if (matIStream) {
2253       LoadMtl(matMap, materials, &matIStream, warn, err);
2254 
2255       return true;
2256     }
2257 
2258     std::stringstream ss;
2259     ss << "Material file [ " << filepath
2260        << " ] not found in a path : " << m_mtlBaseDir << std::endl;
2261     if (warn) {
2262       (*warn) += ss.str();
2263     }
2264 
2265     return false;
2266   }
2267 }
2268 
operator()2269 bool MaterialStreamReader::operator()(const std::string &matId,
2270                                       std::vector<material_t> *materials,
2271                                       std::map<std::string, int> *matMap,
2272                                       std::string *warn, std::string *err) {
2273   (void)err;
2274   (void)matId;
2275   if (!m_inStream) {
2276     std::stringstream ss;
2277     ss << "Material stream in error state. " << std::endl;
2278     if (warn) {
2279       (*warn) += ss.str();
2280     }
2281     return false;
2282   }
2283 
2284   LoadMtl(matMap, materials, &m_inStream, warn, err);
2285 
2286   return true;
2287 }
2288 
LoadObj(attrib_t * attrib,std::vector<shape_t> * shapes,std::vector<material_t> * materials,std::string * warn,std::string * err,const char * filename,const char * mtl_basedir,bool triangulate,bool default_vcols_fallback)2289 bool LoadObj(attrib_t *attrib, std::vector<shape_t> *shapes,
2290              std::vector<material_t> *materials, std::string *warn,
2291              std::string *err, const char *filename, const char *mtl_basedir,
2292              bool triangulate, bool default_vcols_fallback) {
2293   attrib->vertices.clear();
2294   attrib->normals.clear();
2295   attrib->texcoords.clear();
2296   attrib->colors.clear();
2297   shapes->clear();
2298 
2299   std::stringstream errss;
2300 
2301   std::ifstream ifs(filename);
2302   if (!ifs) {
2303     errss << "Cannot open file [" << filename << "]" << std::endl;
2304     if (err) {
2305       (*err) = errss.str();
2306     }
2307     return false;
2308   }
2309 
2310   std::string baseDir = mtl_basedir ? mtl_basedir : "";
2311   if (!baseDir.empty()) {
2312 #ifndef _WIN32
2313     const char dirsep = '/';
2314 #else
2315     const char dirsep = '\\';
2316 #endif
2317     if (baseDir[baseDir.length() - 1] != dirsep) baseDir += dirsep;
2318   }
2319   MaterialFileReader matFileReader(baseDir);
2320 
2321   return LoadObj(attrib, shapes, materials, warn, err, &ifs, &matFileReader,
2322                  triangulate, default_vcols_fallback);
2323 }
2324 
LoadObj(attrib_t * attrib,std::vector<shape_t> * shapes,std::vector<material_t> * materials,std::string * warn,std::string * err,std::istream * inStream,MaterialReader * readMatFn,bool triangulate,bool default_vcols_fallback)2325 bool LoadObj(attrib_t *attrib, std::vector<shape_t> *shapes,
2326              std::vector<material_t> *materials, std::string *warn,
2327              std::string *err, std::istream *inStream,
2328              MaterialReader *readMatFn /*= NULL*/, bool triangulate,
2329              bool default_vcols_fallback) {
2330   std::stringstream errss;
2331 
2332   std::vector<real_t> v;
2333   std::vector<real_t> vn;
2334   std::vector<real_t> vt;
2335   std::vector<real_t> vc;
2336   std::vector<skin_weight_t> vw;
2337   std::vector<tag_t> tags;
2338   PrimGroup prim_group;
2339   std::string name;
2340 
2341   // material
2342   std::map<std::string, int> material_map;
2343   int material = -1;
2344 
2345   // smoothing group id
2346   unsigned int current_smoothing_id =
2347       0;  // Initial value. 0 means no smoothing.
2348 
2349   int greatest_v_idx = -1;
2350   int greatest_vn_idx = -1;
2351   int greatest_vt_idx = -1;
2352 
2353   shape_t shape;
2354 
2355   bool found_all_colors = true;
2356 
2357   size_t line_num = 0;
2358   std::string linebuf;
2359   while (inStream->peek() != -1) {
2360     safeGetline(*inStream, linebuf);
2361 
2362     line_num++;
2363 
2364     // Trim newline '\r\n' or '\n'
2365     if (linebuf.size() > 0) {
2366       if (linebuf[linebuf.size() - 1] == '\n')
2367         linebuf.erase(linebuf.size() - 1);
2368     }
2369     if (linebuf.size() > 0) {
2370       if (linebuf[linebuf.size() - 1] == '\r')
2371         linebuf.erase(linebuf.size() - 1);
2372     }
2373 
2374     // Skip if empty line.
2375     if (linebuf.empty()) {
2376       continue;
2377     }
2378 
2379     // Skip leading space.
2380     const char *token = linebuf.c_str();
2381     token += strspn(token, " \t");
2382 
2383     assert(token);
2384     if (token[0] == '\0') continue;  // empty line
2385 
2386     if (token[0] == '#') continue;  // comment line
2387 
2388     // vertex
2389     if (token[0] == 'v' && IS_SPACE((token[1]))) {
2390       token += 2;
2391       real_t x, y, z;
2392       real_t r, g, b;
2393 
2394       found_all_colors &= parseVertexWithColor(&x, &y, &z, &r, &g, &b, &token);
2395 
2396       v.push_back(x);
2397       v.push_back(y);
2398       v.push_back(z);
2399 
2400       if (found_all_colors || default_vcols_fallback) {
2401         vc.push_back(r);
2402         vc.push_back(g);
2403         vc.push_back(b);
2404       }
2405 
2406       continue;
2407     }
2408 
2409     // normal
2410     if (token[0] == 'v' && token[1] == 'n' && IS_SPACE((token[2]))) {
2411       token += 3;
2412       real_t x, y, z;
2413       parseReal3(&x, &y, &z, &token);
2414       vn.push_back(x);
2415       vn.push_back(y);
2416       vn.push_back(z);
2417       continue;
2418     }
2419 
2420     // texcoord
2421     if (token[0] == 'v' && token[1] == 't' && IS_SPACE((token[2]))) {
2422       token += 3;
2423       real_t x, y;
2424       parseReal2(&x, &y, &token);
2425       vt.push_back(x);
2426       vt.push_back(y);
2427       continue;
2428     }
2429 
2430     // skin weight. tinyobj extension
2431     if (token[0] == 'v' && token[1] == 'w' && IS_SPACE((token[2]))) {
2432       token += 3;
2433 
2434       // vw <vid> <joint_0> <weight_0> <joint_1> <weight_1> ...
2435       // example:
2436       // vw 0 0 0.25 1 0.25 2 0.5
2437 
2438       // TODO(syoyo): Add syntax check
2439       int vid = 0;
2440       vid = parseInt(&token);
2441 
2442       skin_weight_t sw;
2443 
2444       sw.vertex_id = vid;
2445 
2446       while (!IS_NEW_LINE(token[0])) {
2447         real_t j, w;
2448         // joint_id should not be negative, weight may be negative
2449         // TODO(syoyo): # of elements check
2450         parseReal2(&j, &w, &token, -1.0);
2451 
2452         if (j < 0.0) {
2453           if (err) {
2454             std::stringstream ss;
2455             ss << "Failed parse `vw' line. joint_id is negative. "
2456                   "line "
2457                << line_num << ".)\n";
2458             (*err) += ss.str();
2459           }
2460           return false;
2461         }
2462 
2463         joint_and_weight_t jw;
2464 
2465         jw.joint_id = int(j);
2466         jw.weight = w;
2467 
2468         sw.weightValues.push_back(jw);
2469 
2470         size_t n = strspn(token, " \t\r");
2471         token += n;
2472       }
2473 
2474       vw.push_back(sw);
2475     }
2476 
2477     // line
2478     if (token[0] == 'l' && IS_SPACE((token[1]))) {
2479       token += 2;
2480 
2481       __line_t line;
2482 
2483       while (!IS_NEW_LINE(token[0])) {
2484         vertex_index_t vi;
2485         if (!parseTriple(&token, static_cast<int>(v.size() / 3),
2486                          static_cast<int>(vn.size() / 3),
2487                          static_cast<int>(vt.size() / 2), &vi)) {
2488           if (err) {
2489             std::stringstream ss;
2490             ss << "Failed parse `l' line(e.g. zero value for vertex index. "
2491                   "line "
2492                << line_num << ".)\n";
2493             (*err) += ss.str();
2494           }
2495           return false;
2496         }
2497 
2498         line.vertex_indices.push_back(vi);
2499 
2500         size_t n = strspn(token, " \t\r");
2501         token += n;
2502       }
2503 
2504       prim_group.lineGroup.push_back(line);
2505 
2506       continue;
2507     }
2508 
2509     // points
2510     if (token[0] == 'p' && IS_SPACE((token[1]))) {
2511       token += 2;
2512 
2513       __points_t pts;
2514 
2515       while (!IS_NEW_LINE(token[0])) {
2516         vertex_index_t vi;
2517         if (!parseTriple(&token, static_cast<int>(v.size() / 3),
2518                          static_cast<int>(vn.size() / 3),
2519                          static_cast<int>(vt.size() / 2), &vi)) {
2520           if (err) {
2521             std::stringstream ss;
2522             ss << "Failed parse `p' line(e.g. zero value for vertex index. "
2523                   "line "
2524                << line_num << ".)\n";
2525             (*err) += ss.str();
2526           }
2527           return false;
2528         }
2529 
2530         pts.vertex_indices.push_back(vi);
2531 
2532         size_t n = strspn(token, " \t\r");
2533         token += n;
2534       }
2535 
2536       prim_group.pointsGroup.push_back(pts);
2537 
2538       continue;
2539     }
2540 
2541     // face
2542     if (token[0] == 'f' && IS_SPACE((token[1]))) {
2543       token += 2;
2544       token += strspn(token, " \t");
2545 
2546       face_t face;
2547 
2548       face.smoothing_group_id = current_smoothing_id;
2549       face.vertex_indices.reserve(3);
2550 
2551       while (!IS_NEW_LINE(token[0])) {
2552         vertex_index_t vi;
2553         if (!parseTriple(&token, static_cast<int>(v.size() / 3),
2554                          static_cast<int>(vn.size() / 3),
2555                          static_cast<int>(vt.size() / 2), &vi)) {
2556           if (err) {
2557             std::stringstream ss;
2558             ss << "Failed parse `f' line(e.g. zero value for face index. line "
2559                << line_num << ".)\n";
2560             (*err) += ss.str();
2561           }
2562           return false;
2563         }
2564 
2565         greatest_v_idx = greatest_v_idx > vi.v_idx ? greatest_v_idx : vi.v_idx;
2566         greatest_vn_idx =
2567             greatest_vn_idx > vi.vn_idx ? greatest_vn_idx : vi.vn_idx;
2568         greatest_vt_idx =
2569             greatest_vt_idx > vi.vt_idx ? greatest_vt_idx : vi.vt_idx;
2570 
2571         face.vertex_indices.push_back(vi);
2572         size_t n = strspn(token, " \t\r");
2573         token += n;
2574       }
2575 
2576       // replace with emplace_back + std::move on C++11
2577       prim_group.faceGroup.push_back(face);
2578 
2579       continue;
2580     }
2581 
2582     // use mtl
2583     if ((0 == strncmp(token, "usemtl", 6))) {
2584       token += 6;
2585       std::string namebuf = parseString(&token);
2586 
2587       int newMaterialId = -1;
2588       std::map<std::string, int>::const_iterator it =
2589           material_map.find(namebuf);
2590       if (it != material_map.end()) {
2591         newMaterialId = it->second;
2592       } else {
2593         // { error!! material not found }
2594         if (warn) {
2595           (*warn) += "material [ '" + namebuf + "' ] not found in .mtl\n";
2596         }
2597       }
2598 
2599       if (newMaterialId != material) {
2600         // Create per-face material. Thus we don't add `shape` to `shapes` at
2601         // this time.
2602         // just clear `faceGroup` after `exportGroupsToShape()` call.
2603         exportGroupsToShape(&shape, prim_group, tags, material, name,
2604                             triangulate, v, warn);
2605         prim_group.faceGroup.clear();
2606         material = newMaterialId;
2607       }
2608 
2609       continue;
2610     }
2611 
2612     // load mtl
2613     if ((0 == strncmp(token, "mtllib", 6)) && IS_SPACE((token[6]))) {
2614       if (readMatFn) {
2615         token += 7;
2616 
2617         std::vector<std::string> filenames;
2618         SplitString(std::string(token), ' ', '\\', filenames);
2619 
2620         if (filenames.empty()) {
2621           if (warn) {
2622             std::stringstream ss;
2623             ss << "Looks like empty filename for mtllib. Use default "
2624                   "material (line "
2625                << line_num << ".)\n";
2626 
2627             (*warn) += ss.str();
2628           }
2629         } else {
2630           bool found = false;
2631           for (size_t s = 0; s < filenames.size(); s++) {
2632             std::string warn_mtl;
2633             std::string err_mtl;
2634             bool ok = (*readMatFn)(filenames[s].c_str(), materials,
2635                                    &material_map, &warn_mtl, &err_mtl);
2636             if (warn && (!warn_mtl.empty())) {
2637               (*warn) += warn_mtl;
2638             }
2639 
2640             if (err && (!err_mtl.empty())) {
2641               (*err) += err_mtl;
2642             }
2643 
2644             if (ok) {
2645               found = true;
2646               break;
2647             }
2648           }
2649 
2650           if (!found) {
2651             if (warn) {
2652               (*warn) +=
2653                   "Failed to load material file(s). Use default "
2654                   "material.\n";
2655             }
2656           }
2657         }
2658       }
2659 
2660       continue;
2661     }
2662 
2663     // group name
2664     if (token[0] == 'g' && IS_SPACE((token[1]))) {
2665       // flush previous face group.
2666       bool ret = exportGroupsToShape(&shape, prim_group, tags, material, name,
2667                                      triangulate, v, warn);
2668       (void)ret;  // return value not used.
2669 
2670       if (shape.mesh.indices.size() > 0) {
2671         shapes->push_back(shape);
2672       }
2673 
2674       shape = shape_t();
2675 
2676       // material = -1;
2677       prim_group.clear();
2678 
2679       std::vector<std::string> names;
2680 
2681       while (!IS_NEW_LINE(token[0])) {
2682         std::string str = parseString(&token);
2683         names.push_back(str);
2684         token += strspn(token, " \t\r");  // skip tag
2685       }
2686 
2687       // names[0] must be 'g'
2688 
2689       if (names.size() < 2) {
2690         // 'g' with empty names
2691         if (warn) {
2692           std::stringstream ss;
2693           ss << "Empty group name. line: " << line_num << "\n";
2694           (*warn) += ss.str();
2695           name = "";
2696         }
2697       } else {
2698         std::stringstream ss;
2699         ss << names[1];
2700 
2701         // tinyobjloader does not support multiple groups for a primitive.
2702         // Currently we concatinate multiple group names with a space to get
2703         // single group name.
2704 
2705         for (size_t i = 2; i < names.size(); i++) {
2706           ss << " " << names[i];
2707         }
2708 
2709         name = ss.str();
2710       }
2711 
2712       continue;
2713     }
2714 
2715     // object name
2716     if (token[0] == 'o' && IS_SPACE((token[1]))) {
2717       // flush previous face group.
2718       bool ret = exportGroupsToShape(&shape, prim_group, tags, material, name,
2719                                      triangulate, v, warn);
2720       (void)ret;  // return value not used.
2721 
2722       if (shape.mesh.indices.size() > 0 || shape.lines.indices.size() > 0 ||
2723           shape.points.indices.size() > 0) {
2724         shapes->push_back(shape);
2725       }
2726 
2727       // material = -1;
2728       prim_group.clear();
2729       shape = shape_t();
2730 
2731       // @todo { multiple object name? }
2732       token += 2;
2733       std::stringstream ss;
2734       ss << token;
2735       name = ss.str();
2736 
2737       continue;
2738     }
2739 
2740     if (token[0] == 't' && IS_SPACE(token[1])) {
2741       const int max_tag_nums = 8192;  // FIXME(syoyo): Parameterize.
2742       tag_t tag;
2743 
2744       token += 2;
2745 
2746       tag.name = parseString(&token);
2747 
2748       tag_sizes ts = parseTagTriple(&token);
2749 
2750       if (ts.num_ints < 0) {
2751         ts.num_ints = 0;
2752       }
2753       if (ts.num_ints > max_tag_nums) {
2754         ts.num_ints = max_tag_nums;
2755       }
2756 
2757       if (ts.num_reals < 0) {
2758         ts.num_reals = 0;
2759       }
2760       if (ts.num_reals > max_tag_nums) {
2761         ts.num_reals = max_tag_nums;
2762       }
2763 
2764       if (ts.num_strings < 0) {
2765         ts.num_strings = 0;
2766       }
2767       if (ts.num_strings > max_tag_nums) {
2768         ts.num_strings = max_tag_nums;
2769       }
2770 
2771       tag.intValues.resize(static_cast<size_t>(ts.num_ints));
2772 
2773       for (size_t i = 0; i < static_cast<size_t>(ts.num_ints); ++i) {
2774         tag.intValues[i] = parseInt(&token);
2775       }
2776 
2777       tag.floatValues.resize(static_cast<size_t>(ts.num_reals));
2778       for (size_t i = 0; i < static_cast<size_t>(ts.num_reals); ++i) {
2779         tag.floatValues[i] = parseReal(&token);
2780       }
2781 
2782       tag.stringValues.resize(static_cast<size_t>(ts.num_strings));
2783       for (size_t i = 0; i < static_cast<size_t>(ts.num_strings); ++i) {
2784         tag.stringValues[i] = parseString(&token);
2785       }
2786 
2787       tags.push_back(tag);
2788 
2789       continue;
2790     }
2791 
2792     if (token[0] == 's' && IS_SPACE(token[1])) {
2793       // smoothing group id
2794       token += 2;
2795 
2796       // skip space.
2797       token += strspn(token, " \t");  // skip space
2798 
2799       if (token[0] == '\0') {
2800         continue;
2801       }
2802 
2803       if (token[0] == '\r' || token[1] == '\n') {
2804         continue;
2805       }
2806 
2807       if (strlen(token) >= 3 && token[0] == 'o' && token[1] == 'f' &&
2808           token[2] == 'f') {
2809         current_smoothing_id = 0;
2810       } else {
2811         // assume number
2812         int smGroupId = parseInt(&token);
2813         if (smGroupId < 0) {
2814           // parse error. force set to 0.
2815           // FIXME(syoyo): Report warning.
2816           current_smoothing_id = 0;
2817         } else {
2818           current_smoothing_id = static_cast<unsigned int>(smGroupId);
2819         }
2820       }
2821 
2822       continue;
2823     }  // smoothing group id
2824 
2825     // Ignore unknown command.
2826   }
2827 
2828   // not all vertices have colors, no default colors desired? -> clear colors
2829   if (!found_all_colors && !default_vcols_fallback) {
2830     vc.clear();
2831   }
2832 
2833   if (greatest_v_idx >= static_cast<int>(v.size() / 3)) {
2834     if (warn) {
2835       std::stringstream ss;
2836       ss << "Vertex indices out of bounds (line " << line_num << ".)\n"
2837          << std::endl;
2838       (*warn) += ss.str();
2839     }
2840   }
2841   if (greatest_vn_idx >= static_cast<int>(vn.size() / 3)) {
2842     if (warn) {
2843       std::stringstream ss;
2844       ss << "Vertex normal indices out of bounds (line " << line_num << ".)\n"
2845          << std::endl;
2846       (*warn) += ss.str();
2847     }
2848   }
2849   if (greatest_vt_idx >= static_cast<int>(vt.size() / 2)) {
2850     if (warn) {
2851       std::stringstream ss;
2852       ss << "Vertex texcoord indices out of bounds (line " << line_num << ".)\n"
2853          << std::endl;
2854       (*warn) += ss.str();
2855     }
2856   }
2857 
2858   bool ret = exportGroupsToShape(&shape, prim_group, tags, material, name,
2859                                  triangulate, v, warn);
2860   // exportGroupsToShape return false when `usemtl` is called in the last
2861   // line.
2862   // we also add `shape` to `shapes` when `shape.mesh` has already some
2863   // faces(indices)
2864   if (ret || shape.mesh.indices
2865                  .size()) {  // FIXME(syoyo): Support other prims(e.g. lines)
2866     shapes->push_back(shape);
2867   }
2868   prim_group.clear();  // for safety
2869 
2870   if (err) {
2871     (*err) += errss.str();
2872   }
2873 
2874   attrib->vertices.swap(v);
2875   attrib->vertex_weights.swap(v);
2876   attrib->normals.swap(vn);
2877   attrib->texcoords.swap(vt);
2878   attrib->texcoord_ws.swap(vt);
2879   attrib->colors.swap(vc);
2880   attrib->skin_weights.swap(vw);
2881 
2882   return true;
2883 }
2884 
LoadObjWithCallback(std::istream & inStream,const callback_t & callback,void * user_data,MaterialReader * readMatFn,std::string * warn,std::string * err)2885 bool LoadObjWithCallback(std::istream &inStream, const callback_t &callback,
2886                          void *user_data /*= NULL*/,
2887                          MaterialReader *readMatFn /*= NULL*/,
2888                          std::string *warn, /* = NULL*/
2889                          std::string *err /*= NULL*/) {
2890   std::stringstream errss;
2891 
2892   // material
2893   std::map<std::string, int> material_map;
2894   int material_id = -1;  // -1 = invalid
2895 
2896   std::vector<index_t> indices;
2897   std::vector<material_t> materials;
2898   std::vector<std::string> names;
2899   names.reserve(2);
2900   std::vector<const char *> names_out;
2901 
2902   std::string linebuf;
2903   while (inStream.peek() != -1) {
2904     safeGetline(inStream, linebuf);
2905 
2906     // Trim newline '\r\n' or '\n'
2907     if (linebuf.size() > 0) {
2908       if (linebuf[linebuf.size() - 1] == '\n')
2909         linebuf.erase(linebuf.size() - 1);
2910     }
2911     if (linebuf.size() > 0) {
2912       if (linebuf[linebuf.size() - 1] == '\r')
2913         linebuf.erase(linebuf.size() - 1);
2914     }
2915 
2916     // Skip if empty line.
2917     if (linebuf.empty()) {
2918       continue;
2919     }
2920 
2921     // Skip leading space.
2922     const char *token = linebuf.c_str();
2923     token += strspn(token, " \t");
2924 
2925     assert(token);
2926     if (token[0] == '\0') continue;  // empty line
2927 
2928     if (token[0] == '#') continue;  // comment line
2929 
2930     // vertex
2931     if (token[0] == 'v' && IS_SPACE((token[1]))) {
2932       token += 2;
2933       // TODO(syoyo): Support parsing vertex color extension.
2934       real_t x, y, z, w;  // w is optional. default = 1.0
2935       parseV(&x, &y, &z, &w, &token);
2936       if (callback.vertex_cb) {
2937         callback.vertex_cb(user_data, x, y, z, w);
2938       }
2939       continue;
2940     }
2941 
2942     // normal
2943     if (token[0] == 'v' && token[1] == 'n' && IS_SPACE((token[2]))) {
2944       token += 3;
2945       real_t x, y, z;
2946       parseReal3(&x, &y, &z, &token);
2947       if (callback.normal_cb) {
2948         callback.normal_cb(user_data, x, y, z);
2949       }
2950       continue;
2951     }
2952 
2953     // texcoord
2954     if (token[0] == 'v' && token[1] == 't' && IS_SPACE((token[2]))) {
2955       token += 3;
2956       real_t x, y, z;  // y and z are optional. default = 0.0
2957       parseReal3(&x, &y, &z, &token);
2958       if (callback.texcoord_cb) {
2959         callback.texcoord_cb(user_data, x, y, z);
2960       }
2961       continue;
2962     }
2963 
2964     // face
2965     if (token[0] == 'f' && IS_SPACE((token[1]))) {
2966       token += 2;
2967       token += strspn(token, " \t");
2968 
2969       indices.clear();
2970       while (!IS_NEW_LINE(token[0])) {
2971         vertex_index_t vi = parseRawTriple(&token);
2972 
2973         index_t idx;
2974         idx.vertex_index = vi.v_idx;
2975         idx.normal_index = vi.vn_idx;
2976         idx.texcoord_index = vi.vt_idx;
2977 
2978         indices.push_back(idx);
2979         size_t n = strspn(token, " \t\r");
2980         token += n;
2981       }
2982 
2983       if (callback.index_cb && indices.size() > 0) {
2984         callback.index_cb(user_data, &indices.at(0),
2985                           static_cast<int>(indices.size()));
2986       }
2987 
2988       continue;
2989     }
2990 
2991     // use mtl
2992     if ((0 == strncmp(token, "usemtl", 6)) && IS_SPACE((token[6]))) {
2993       token += 7;
2994       std::stringstream ss;
2995       ss << token;
2996       std::string namebuf = ss.str();
2997 
2998       int newMaterialId = -1;
2999       std::map<std::string, int>::const_iterator it =
3000           material_map.find(namebuf);
3001       if (it != material_map.end()) {
3002         newMaterialId = it->second;
3003       } else {
3004         // { warn!! material not found }
3005         if (warn && (!callback.usemtl_cb)) {
3006           (*warn) += "material [ " + namebuf + " ] not found in .mtl\n";
3007         }
3008       }
3009 
3010       if (newMaterialId != material_id) {
3011         material_id = newMaterialId;
3012       }
3013 
3014       if (callback.usemtl_cb) {
3015         callback.usemtl_cb(user_data, namebuf.c_str(), material_id);
3016       }
3017 
3018       continue;
3019     }
3020 
3021     // load mtl
3022     if ((0 == strncmp(token, "mtllib", 6)) && IS_SPACE((token[6]))) {
3023       if (readMatFn) {
3024         token += 7;
3025 
3026         std::vector<std::string> filenames;
3027         SplitString(std::string(token), ' ', '\\', filenames);
3028 
3029         if (filenames.empty()) {
3030           if (warn) {
3031             (*warn) +=
3032                 "Looks like empty filename for mtllib. Use default "
3033                 "material. \n";
3034           }
3035         } else {
3036           bool found = false;
3037           for (size_t s = 0; s < filenames.size(); s++) {
3038             std::string warn_mtl;
3039             std::string err_mtl;
3040             bool ok = (*readMatFn)(filenames[s].c_str(), &materials,
3041                                    &material_map, &warn_mtl, &err_mtl);
3042 
3043             if (warn && (!warn_mtl.empty())) {
3044               (*warn) += warn_mtl;  // This should be warn message.
3045             }
3046 
3047             if (err && (!err_mtl.empty())) {
3048               (*err) += err_mtl;
3049             }
3050 
3051             if (ok) {
3052               found = true;
3053               break;
3054             }
3055           }
3056 
3057           if (!found) {
3058             if (warn) {
3059               (*warn) +=
3060                   "Failed to load material file(s). Use default "
3061                   "material.\n";
3062             }
3063           } else {
3064             if (callback.mtllib_cb) {
3065               callback.mtllib_cb(user_data, &materials.at(0),
3066                                  static_cast<int>(materials.size()));
3067             }
3068           }
3069         }
3070       }
3071 
3072       continue;
3073     }
3074 
3075     // group name
3076     if (token[0] == 'g' && IS_SPACE((token[1]))) {
3077       names.clear();
3078 
3079       while (!IS_NEW_LINE(token[0])) {
3080         std::string str = parseString(&token);
3081         names.push_back(str);
3082         token += strspn(token, " \t\r");  // skip tag
3083       }
3084 
3085       assert(names.size() > 0);
3086 
3087       if (callback.group_cb) {
3088         if (names.size() > 1) {
3089           // create const char* array.
3090           names_out.resize(names.size() - 1);
3091           for (size_t j = 0; j < names_out.size(); j++) {
3092             names_out[j] = names[j + 1].c_str();
3093           }
3094           callback.group_cb(user_data, &names_out.at(0),
3095                             static_cast<int>(names_out.size()));
3096 
3097         } else {
3098           callback.group_cb(user_data, NULL, 0);
3099         }
3100       }
3101 
3102       continue;
3103     }
3104 
3105     // object name
3106     if (token[0] == 'o' && IS_SPACE((token[1]))) {
3107       // @todo { multiple object name? }
3108       token += 2;
3109 
3110       std::stringstream ss;
3111       ss << token;
3112       std::string object_name = ss.str();
3113 
3114       if (callback.object_cb) {
3115         callback.object_cb(user_data, object_name.c_str());
3116       }
3117 
3118       continue;
3119     }
3120 
3121 #if 0  // @todo
3122     if (token[0] == 't' && IS_SPACE(token[1])) {
3123       tag_t tag;
3124 
3125       token += 2;
3126       std::stringstream ss;
3127       ss << token;
3128       tag.name = ss.str();
3129 
3130       token += tag.name.size() + 1;
3131 
3132       tag_sizes ts = parseTagTriple(&token);
3133 
3134       tag.intValues.resize(static_cast<size_t>(ts.num_ints));
3135 
3136       for (size_t i = 0; i < static_cast<size_t>(ts.num_ints); ++i) {
3137         tag.intValues[i] = atoi(token);
3138         token += strcspn(token, "/ \t\r") + 1;
3139       }
3140 
3141       tag.floatValues.resize(static_cast<size_t>(ts.num_reals));
3142       for (size_t i = 0; i < static_cast<size_t>(ts.num_reals); ++i) {
3143         tag.floatValues[i] = parseReal(&token);
3144         token += strcspn(token, "/ \t\r") + 1;
3145       }
3146 
3147       tag.stringValues.resize(static_cast<size_t>(ts.num_strings));
3148       for (size_t i = 0; i < static_cast<size_t>(ts.num_strings); ++i) {
3149         std::stringstream ss;
3150         ss << token;
3151         tag.stringValues[i] = ss.str();
3152         token += tag.stringValues[i].size() + 1;
3153       }
3154 
3155       tags.push_back(tag);
3156     }
3157 #endif
3158 
3159     // Ignore unknown command.
3160   }
3161 
3162   if (err) {
3163     (*err) += errss.str();
3164   }
3165 
3166   return true;
3167 }
3168 
ParseFromFile(const std::string & filename,const ObjReaderConfig & config)3169 bool ObjReader::ParseFromFile(const std::string &filename,
3170                               const ObjReaderConfig &config) {
3171   std::string mtl_search_path;
3172 
3173   if (config.mtl_search_path.empty()) {
3174     //
3175     // split at last '/'(for unixish system) or '\\'(for windows) to get
3176     // the base directory of .obj file
3177     //
3178     size_t pos = filename.find_last_of("/\\");
3179     if (pos != std::string::npos) {
3180       mtl_search_path = filename.substr(0, pos);
3181     }
3182   } else {
3183     mtl_search_path = config.mtl_search_path;
3184   }
3185 
3186   valid_ = LoadObj(&attrib_, &shapes_, &materials_, &warning_, &error_,
3187                    filename.c_str(), mtl_search_path.c_str(),
3188                    config.triangulate, config.vertex_color);
3189 
3190   return valid_;
3191 }
3192 
ParseFromString(const std::string & obj_text,const std::string & mtl_text,const ObjReaderConfig & config)3193 bool ObjReader::ParseFromString(const std::string &obj_text,
3194                                 const std::string &mtl_text,
3195                                 const ObjReaderConfig &config) {
3196   std::stringbuf obj_buf(obj_text);
3197   std::stringbuf mtl_buf(mtl_text);
3198 
3199   std::istream obj_ifs(&obj_buf);
3200   std::istream mtl_ifs(&mtl_buf);
3201 
3202   MaterialStreamReader mtl_ss(mtl_ifs);
3203 
3204   valid_ = LoadObj(&attrib_, &shapes_, &materials_, &warning_, &error_,
3205                    &obj_ifs, &mtl_ss, config.triangulate, config.vertex_color);
3206 
3207   return valid_;
3208 }
3209 
3210 #ifdef __clang__
3211 #pragma clang diagnostic pop
3212 #endif
3213 }  // namespace tinyobj
3214 
3215 #endif
3216