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