1 /*
2 The MIT License (MIT)
3 
4 Copyright (c) 2012-2017 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 1.0.7 : Support multiple tex options(#126)
27 // version 1.0.6 : Add TINYOBJLOADER_USE_DOUBLE option(#124)
28 // version 1.0.5 : Ignore `Tr` when `d` exists in MTL(#43)
29 // version 1.0.4 : Support multiple filenames for 'mtllib'(#112)
30 // version 1.0.3 : Support parsing texture options(#85)
31 // version 1.0.2 : Improve parsing speed by about a factor of 2 for large
32 // files(#105)
33 // version 1.0.1 : Fixes a shape is lost if obj ends with a 'usemtl'(#104)
34 // version 1.0.0 : Change data structure. Change license from BSD to MIT.
35 //
36 
37 //
38 // Use this in *one* .cc
39 //   #define IGNITION_COMMON_TINYOBJLOADER_IMPLEMENTATION
40 //   #include "tiny_obj_loader.h"
41 //
42 
43 #ifndef IGNITION_COMMON_TINY_OBJ_LOADER_H_
44 #define IGNITION_COMMON_TINY_OBJ_LOADER_H_
45 
46 #include <map>
47 #include <string>
48 #include <vector>
49 
50 namespace ignition
51 {
52 namespace tinyobj {
53 
54 // https://en.wikipedia.org/wiki/Wavefront_.obj_file says ...
55 //
56 //  -blendu on | off                       # set horizontal texture blending
57 //  (default on)
58 //  -blendv on | off                       # set vertical texture blending
59 //  (default on)
60 //  -boost real_value                      # boost mip-map sharpness
61 //  -mm base_value gain_value              # modify texture map values (default
62 //  0 1)
63 //                                         #     base_value = brightness,
64 //                                         gain_value = contrast
65 //  -o u [v [w]]                           # Origin offset             (default
66 //  0 0 0)
67 //  -s u [v [w]]                           # Scale                     (default
68 //  1 1 1)
69 //  -t u [v [w]]                           # Turbulence                (default
70 //  0 0 0)
71 //  -texres resolution                     # texture resolution to create
72 //  -clamp on | off                        # only render texels in the clamped
73 //  0-1 range (default off)
74 //                                         #   When unclamped, textures are
75 //                                         repeated across a surface,
76 //                                         #   when clamped, only texels which
77 //                                         fall within the 0-1
78 //                                         #   range are rendered.
79 //  -bm mult_value                         # bump multiplier (for bump maps
80 //  only)
81 //
82 //  -imfchan r | g | b | m | l | z         # specifies which channel of the file
83 //  is used to
84 //                                         # create a scalar or bump texture.
85 //                                         r:red, g:green,
86 //                                         # b:blue, m:matte, l:luminance,
87 //                                         z:z-depth..
88 //                                         # (the default for bump is 'l' and
89 //                                         for decal is 'm')
90 //  bump -imfchan r bumpmap.tga            # says to use the red channel of
91 //  bumpmap.tga as the bumpmap
92 //
93 // For reflection maps...
94 //
95 //   -type sphere                           # specifies a sphere for a "refl"
96 //   reflection map
97 //   -type cube_top    | cube_bottom |      # when using a cube map, the texture
98 //   file for each
99 //         cube_front  | cube_back   |      # side of the cube is specified
100 //         separately
101 //         cube_left   | cube_right
102 
103 #ifdef TINYOBJLOADER_USE_DOUBLE
104   //#pragma message "using double"
105   typedef double real_t;
106 #else
107   //#pragma message "using float"
108   typedef float real_t;
109 #endif
110 
111 typedef enum {
112   TEXTURE_TYPE_NONE,  // default
113   TEXTURE_TYPE_SPHERE,
114   TEXTURE_TYPE_CUBE_TOP,
115   TEXTURE_TYPE_CUBE_BOTTOM,
116   TEXTURE_TYPE_CUBE_FRONT,
117   TEXTURE_TYPE_CUBE_BACK,
118   TEXTURE_TYPE_CUBE_LEFT,
119   TEXTURE_TYPE_CUBE_RIGHT
120 } texture_type_t;
121 
122 typedef struct {
123   texture_type_t type;     // -type (default TEXTURE_TYPE_NONE)
124   real_t sharpness;         // -boost (default 1.0?)
125   real_t brightness;        // base_value in -mm option (default 0)
126   real_t contrast;          // gain_value in -mm option (default 1)
127   real_t origin_offset[3];  // -o u [v [w]] (default 0 0 0)
128   real_t scale[3];          // -s u [v [w]] (default 1 1 1)
129   real_t turbulence[3];     // -t u [v [w]] (default 0 0 0)
130   // int   texture_resolution; // -texres resolution (default = ?) TODO
131   bool clamp;    // -clamp (default false)
132   char imfchan;  // -imfchan (the default for bump is 'l' and for decal is 'm')
133   bool blendu;   // -blendu (default on)
134   bool blendv;   // -blendv (default on)
135   real_t bump_multiplier;  // -bm (for bump maps only, default 1.0)
136 } texture_option_t;
137 
138 typedef struct {
139   std::string name;
140 
141   real_t ambient[3];
142   real_t diffuse[3];
143   real_t specular[3];
144   real_t transmittance[3];
145   real_t emission[3];
146   real_t shininess;
147   real_t ior;       // index of refraction
148   real_t dissolve;  // 1 == opaque; 0 == fully transparent
149   // illumination model (see http://www.fileformat.info/format/material/)
150   int illum;
151 
152   int dummy;  // Suppress padding warning.
153 
154   std::string ambient_texname;             // map_Ka
155   std::string diffuse_texname;             // map_Kd
156   std::string specular_texname;            // map_Ks
157   std::string specular_highlight_texname;  // map_Ns
158   std::string bump_texname;                // map_bump, bump
159   std::string displacement_texname;        // disp
160   std::string alpha_texname;               // map_d
161   std::string reflection_texname;          // refl
162 
163   texture_option_t ambient_texopt;
164   texture_option_t diffuse_texopt;
165   texture_option_t specular_texopt;
166   texture_option_t specular_highlight_texopt;
167   texture_option_t bump_texopt;
168   texture_option_t displacement_texopt;
169   texture_option_t alpha_texopt;
170   texture_option_t reflection_texopt;
171 
172   // PBR extension
173   // http://exocortex.com/blog/extending_wavefront_mtl_to_support_pbr
174   real_t roughness;            // [0, 1] default 0
175   real_t metallic;             // [0, 1] default 0
176   real_t sheen;                // [0, 1] default 0
177   real_t clearcoat_thickness;  // [0, 1] default 0
178   real_t clearcoat_roughness;  // [0, 1] default 0
179   real_t anisotropy;           // aniso. [0, 1] default 0
180   real_t anisotropy_rotation;  // anisor. [0, 1] default 0
181   real_t pad0;
182   std::string roughness_texname;  // map_Pr
183   std::string metallic_texname;   // map_Pm
184   std::string sheen_texname;      // map_Ps
185   std::string emissive_texname;   // map_Ke
186   std::string normal_texname;     // norm. For normal mapping.
187 
188   texture_option_t roughness_texopt;
189   texture_option_t metallic_texopt;
190   texture_option_t sheen_texopt;
191   texture_option_t emissive_texopt;
192   texture_option_t normal_texopt;
193 
194   int pad2;
195 
196   std::map<std::string, std::string> unknown_parameter;
197 } material_t;
198 
199 typedef struct {
200   std::string name;
201 
202   std::vector<int> intValues;
203   std::vector<real_t> floatValues;
204   std::vector<std::string> stringValues;
205 } tag_t;
206 
207 // Index struct to support different indices for vtx/normal/texcoord.
208 // -1 means not used.
209 typedef struct {
210   int vertex_index;
211   int normal_index;
212   int texcoord_index;
213 } index_t;
214 
215 typedef struct {
216   std::vector<index_t> indices;
217   std::vector<unsigned char> num_face_vertices;  // The number of vertices per
218                                                  // face. 3 = polygon, 4 = quad,
219                                                  // ... Up to 255.
220   std::vector<int> material_ids;                 // per-face material ID
221   std::vector<tag_t> tags;                       // SubD tag
222 } mesh_t;
223 
224 typedef struct {
225   std::string name;
226   mesh_t mesh;
227 } shape_t;
228 
229 // Vertex attributes
230 typedef struct {
231   std::vector<real_t> vertices;   // 'v'
232   std::vector<real_t> normals;    // 'vn'
233   std::vector<real_t> texcoords;  // 'vt'
234 } attrib_t;
235 
236 typedef struct callback_t_ {
237   // W is optional and set to 1 if there is no `w` item in `v` line
238   void (*vertex_cb)(void *user_data, real_t x, real_t y, real_t z, real_t w);
239   void (*normal_cb)(void *user_data, real_t x, real_t y, real_t z);
240 
241   // y and z are optional and set to 0 if there is no `y` and/or `z` item(s) in
242   // `vt` line.
243   void (*texcoord_cb)(void *user_data, real_t x, real_t y, real_t z);
244 
245   // called per 'f' line. num_indices is the number of face indices(e.g. 3 for
246   // triangle, 4 for quad)
247   // 0 will be passed for undefined index in index_t members.
248   void (*index_cb)(void *user_data, index_t *indices, int num_indices);
249   // `name` material name, `material_id` = the array index of material_t[]. -1
250   // if
251   // a material not found in .mtl
252   void (*usemtl_cb)(void *user_data, const char *name, int material_id);
253   // `materials` = parsed material data.
254   void (*mtllib_cb)(void *user_data, const material_t *materials,
255                     int num_materials);
256   // There may be multiple group names
257   void (*group_cb)(void *user_data, const char **names, int num_names);
258   void (*object_cb)(void *user_data, const char *name);
259 
callback_t_callback_t_260   callback_t_()
261       : vertex_cb(NULL),
262         normal_cb(NULL),
263         texcoord_cb(NULL),
264         index_cb(NULL),
265         usemtl_cb(NULL),
266         mtllib_cb(NULL),
267         group_cb(NULL),
268         object_cb(NULL) {}
269 } callback_t;
270 
271 class MaterialReader {
272  public:
MaterialReader()273   MaterialReader() {}
274   virtual ~MaterialReader();
275 
276   virtual bool operator()(const std::string &matId,
277                           std::vector<material_t> *materials,
278                           std::map<std::string, int> *matMap,
279                           std::string *err) = 0;
280 };
281 
282 class MaterialFileReader : public MaterialReader {
283  public:
MaterialFileReader(const std::string & mtl_basedir)284   explicit MaterialFileReader(const std::string &mtl_basedir)
285       : m_mtlBaseDir(mtl_basedir) {}
~MaterialFileReader()286   virtual ~MaterialFileReader() {}
287   virtual bool operator()(const std::string &matId,
288                           std::vector<material_t> *materials,
289                           std::map<std::string, int> *matMap, std::string *err);
290 
291  private:
292   std::string m_mtlBaseDir;
293 };
294 
295 class MaterialStreamReader : public MaterialReader {
296  public:
MaterialStreamReader(std::istream & inStream)297   explicit MaterialStreamReader(std::istream &inStream)
298       : m_inStream(inStream) {}
~MaterialStreamReader()299   virtual ~MaterialStreamReader() {}
300   virtual bool operator()(const std::string &matId,
301                           std::vector<material_t> *materials,
302                           std::map<std::string, int> *matMap, std::string *err);
303 
304  private:
305   std::istream &m_inStream;
306 };
307 
308 /// Loads .obj from a file.
309 /// 'attrib', 'shapes' and 'materials' will be filled with parsed shape data
310 /// 'shapes' will be filled with parsed shape data
311 /// Returns true when loading .obj become success.
312 /// Returns warning and error message into `err`
313 /// 'mtl_basedir' is optional, and used for base directory for .mtl file.
314 /// In default(`NULL'), .mtl file is searched from an application's working
315 /// directory.
316 /// 'triangulate' is optional, and used whether triangulate polygon face in .obj
317 /// or not.
318 bool LoadObj(attrib_t *attrib, std::vector<shape_t> *shapes,
319              std::vector<material_t> *materials, std::string *err,
320              const char *filename, const char *mtl_basedir = NULL,
321              bool triangulate = true);
322 
323 /// Loads .obj from a file with custom user callback.
324 /// .mtl is loaded as usual and parsed material_t data will be passed to
325 /// `callback.mtllib_cb`.
326 /// Returns true when loading .obj/.mtl become success.
327 /// Returns warning and error message into `err`
328 /// See `examples/callback_api/` for how to use this function.
329 bool LoadObjWithCallback(std::istream &inStream, const callback_t &callback,
330                          void *user_data = NULL,
331                          MaterialReader *readMatFn = NULL,
332                          std::string *err = NULL);
333 
334 /// Loads object from a std::istream, uses GetMtlIStreamFn to retrieve
335 /// std::istream for materials.
336 /// Returns true when loading .obj become success.
337 /// Returns warning and error message into `err`
338 bool LoadObj(attrib_t *attrib, std::vector<shape_t> *shapes,
339              std::vector<material_t> *materials, std::string *err,
340              std::istream *inStream, MaterialReader *readMatFn = NULL,
341              bool triangulate = true);
342 
343 /// Loads materials into std::map
344 void LoadMtl(std::map<std::string, int> *material_map,
345              std::vector<material_t> *materials, std::istream *inStream,
346              std::string *warning);
347 
348 }  // namespace tinyobj
349 }  // namespace ignition
350 
351 #endif  // IGNITION_COMMON_TINY_OBJ_LOADER_H_
352 
353 #ifdef IGNITION_COMMON_TINYOBJLOADER_IMPLEMENTATION
354 #include <cassert>
355 #include <cctype>
356 #include <cmath>
357 #include <cstddef>
358 #include <cstdlib>
359 #include <cstring>
360 #include <utility>
361 
362 #include <fstream>
363 #include <sstream>
364 
365 namespace ignition
366 {
367 namespace tinyobj {
368 
~MaterialReader()369 MaterialReader::~MaterialReader() {}
370 
371 
372 struct vertex_index {
373   int v_idx, vt_idx, vn_idx;
vertex_indexvertex_index374   vertex_index() : v_idx(-1), vt_idx(-1), vn_idx(-1) {}
vertex_indexvertex_index375   explicit vertex_index(int idx) : v_idx(idx), vt_idx(idx), vn_idx(idx) {}
vertex_indexvertex_index376   vertex_index(int vidx, int vtidx, int vnidx)
377       : v_idx(vidx), vt_idx(vtidx), vn_idx(vnidx) {}
378 };
379 
380 struct tag_sizes {
tag_sizestag_sizes381   tag_sizes() : num_ints(0), num_reals(0), num_strings(0) {}
382   int num_ints;
383   int num_reals;
384   int num_strings;
385 };
386 
387 struct obj_shape {
388   std::vector<real_t> v;
389   std::vector<real_t> vn;
390   std::vector<real_t> vt;
391 };
392 
393 // See
394 // http://stackoverflow.com/questions/6089231/getting-std-ifstream-to-handle-lf-cr-and-crlf
safeGetline(std::istream & is,std::string & t)395 static std::istream &safeGetline(std::istream &is, std::string &t) {
396   t.clear();
397 
398   // The characters in the stream are read one-by-one using a std::streambuf.
399   // That is faster than reading them one-by-one using the std::istream.
400   // Code that uses streambuf this way must be guarded by a sentry object.
401   // The sentry object performs various tasks,
402   // such as thread synchronization and updating the stream state.
403 
404   std::istream::sentry se(is, true);
405   std::streambuf *sb = is.rdbuf();
406 
407   if (se) {
408     for (;;) {
409       int c = sb->sbumpc();
410       switch (c) {
411         case '\n':
412           return is;
413         case '\r':
414           if (sb->sgetc() == '\n') sb->sbumpc();
415           return is;
416         case EOF:
417           // Also handle the case when the last line has no line ending
418           if (t.empty()) is.setstate(std::ios::eofbit);
419           return is;
420         default:
421           t += static_cast<char>(c);
422       }
423     }
424   }
425 
426   return is;
427 }
428 
429 #define IS_SPACE(x) (((x) == ' ') || ((x) == '\t'))
430 #define IS_DIGIT(x) \
431   (static_cast<unsigned int>((x) - '0') < static_cast<unsigned int>(10))
432 #define IS_NEW_LINE(x) (((x) == '\r') || ((x) == '\n') || ((x) == '\0'))
433 
434 // Make index zero-base, and also support relative index.
fixIndex(int idx,int n)435 static inline int fixIndex(int idx, int n) {
436   if (idx > 0) return idx - 1;
437   if (idx == 0) return 0;
438   return n + idx;  // negative value = relative
439 }
440 
parseString(const char ** token)441 static inline std::string parseString(const char **token) {
442   std::string s;
443   (*token) += strspn((*token), " \t");
444   size_t e = strcspn((*token), " \t\r");
445   s = std::string((*token), &(*token)[e]);
446   (*token) += e;
447   return s;
448 }
449 
parseInt(const char ** token)450 static inline int parseInt(const char **token) {
451   (*token) += strspn((*token), " \t");
452   int i = atoi((*token));
453   (*token) += strcspn((*token), " \t\r");
454   return i;
455 }
456 
457 // Tries to parse a floating point number located at s.
458 //
459 // s_end should be a location in the string where reading should absolutely
460 // stop. For example at the end of the string, to prevent buffer overflows.
461 //
462 // Parses the following EBNF grammar:
463 //   sign    = "+" | "-" ;
464 //   END     = ? anything not in digit ?
465 //   digit   = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" ;
466 //   integer = [sign] , digit , {digit} ;
467 //   decimal = integer , ["." , integer] ;
468 //   float   = ( decimal , END ) | ( decimal , ("E" | "e") , integer , END ) ;
469 //
470 //  Valid strings are for example:
471 //   -0  +3.1417e+2  -0.0E-3  1.0324  -1.41   11e2
472 //
473 // If the parsing is a success, result is set to the parsed value and true
474 // is returned.
475 //
476 // The function is greedy and will parse until any of the following happens:
477 //  - a non-conforming character is encountered.
478 //  - s_end is reached.
479 //
480 // The following situations triggers a failure:
481 //  - s >= s_end.
482 //  - parse failure.
483 //
tryParseDouble(const char * s,const char * s_end,double * result)484 static bool tryParseDouble(const char *s, const char *s_end, double *result) {
485   if (s >= s_end) {
486     return false;
487   }
488 
489   double mantissa = 0.0;
490   // This exponent is base 2 rather than 10.
491   // However the exponent we parse is supposed to be one of ten,
492   // thus we must take care to convert the exponent/and or the
493   // mantissa to a * 2^E, where a is the mantissa and E is the
494   // exponent.
495   // To get the final double we will use ldexp, it requires the
496   // exponent to be in base 2.
497   int exponent = 0;
498 
499   // NOTE: THESE MUST BE DECLARED HERE SINCE WE ARE NOT ALLOWED
500   // TO JUMP OVER DEFINITIONS.
501   char sign = '+';
502   char exp_sign = '+';
503   char const *curr = s;
504 
505   // How many characters were read in a loop.
506   int read = 0;
507   // Tells whether a loop terminated due to reaching s_end.
508   bool end_not_reached = false;
509 
510   /*
511           BEGIN PARSING.
512   */
513 
514   // Find out what sign we've got.
515   if (*curr == '+' || *curr == '-') {
516     sign = *curr;
517     curr++;
518   } else if (IS_DIGIT(*curr)) { /* Pass through. */
519   } else {
520     goto fail;
521   }
522 
523   // Read the integer part.
524   end_not_reached = (curr != s_end);
525   while (end_not_reached && IS_DIGIT(*curr)) {
526     mantissa *= 10;
527     mantissa += static_cast<int>(*curr - 0x30);
528     curr++;
529     read++;
530     end_not_reached = (curr != s_end);
531   }
532 
533   // We must make sure we actually got something.
534   if (read == 0) goto fail;
535   // We allow numbers of form "#", "###" etc.
536   if (!end_not_reached) goto assemble;
537 
538   // Read the decimal part.
539   if (*curr == '.') {
540     curr++;
541     read = 1;
542     end_not_reached = (curr != s_end);
543     while (end_not_reached && IS_DIGIT(*curr)) {
544       static const double pow_lut[] = {
545           1.0, 0.1, 0.01, 0.001, 0.0001, 0.00001, 0.000001, 0.0000001,
546       };
547       const int lut_entries = sizeof pow_lut / sizeof pow_lut[0];
548 
549       // NOTE: Don't use powf here, it will absolutely murder precision.
550       mantissa += static_cast<int>(*curr - 0x30) *
551                   (read < lut_entries ? pow_lut[read] : std::pow(10.0, -read));
552       read++;
553       curr++;
554       end_not_reached = (curr != s_end);
555     }
556   } else if (*curr == 'e' || *curr == 'E') {
557   } else {
558     goto assemble;
559   }
560 
561   if (!end_not_reached) goto assemble;
562 
563   // Read the exponent part.
564   if (*curr == 'e' || *curr == 'E') {
565     curr++;
566     // Figure out if a sign is present and if it is.
567     end_not_reached = (curr != s_end);
568     if (end_not_reached && (*curr == '+' || *curr == '-')) {
569       exp_sign = *curr;
570       curr++;
571     } else if (IS_DIGIT(*curr)) { /* Pass through. */
572     } else {
573       // Empty E is not allowed.
574       goto fail;
575     }
576 
577     read = 0;
578     end_not_reached = (curr != s_end);
579     while (end_not_reached && IS_DIGIT(*curr)) {
580       exponent *= 10;
581       exponent += static_cast<int>(*curr - 0x30);
582       curr++;
583       read++;
584       end_not_reached = (curr != s_end);
585     }
586     exponent *= (exp_sign == '+' ? 1 : -1);
587     if (read == 0) goto fail;
588   }
589 
590 assemble:
591   *result =
592       (sign == '+' ? 1 : -1) *
593       (exponent ? std::ldexp(mantissa * std::pow(5.0, exponent), exponent) : mantissa);
594   return true;
595 fail:
596   return false;
597 }
598 
599 static inline real_t parseReal(const char **token, double default_value = 0.0) {
600   (*token) += strspn((*token), " \t");
601   const char *end = (*token) + strcspn((*token), " \t\r");
602   double val = default_value;
603   tryParseDouble((*token), end, &val);
604   real_t f = static_cast<real_t>(val);
605   (*token) = end;
606   return f;
607 }
608 
609 static inline void parseReal2(real_t *x, real_t *y, const char **token,
610                                const double default_x = 0.0,
611                                const double default_y = 0.0) {
612   (*x) = parseReal(token, default_x);
613   (*y) = parseReal(token, default_y);
614 }
615 
616 static inline void parseReal3(real_t *x, real_t *y, real_t *z, const char **token,
617                                const double default_x = 0.0,
618                                const double default_y = 0.0,
619                                const double default_z = 0.0) {
620   (*x) = parseReal(token, default_x);
621   (*y) = parseReal(token, default_y);
622   (*z) = parseReal(token, default_z);
623 }
624 
625 static inline void parseV(real_t *x, real_t *y, real_t *z, real_t *w,
626                           const char **token, const double default_x = 0.0,
627                           const double default_y = 0.0,
628                           const double default_z = 0.0,
629                           const double default_w = 1.0) {
630   (*x) = parseReal(token, default_x);
631   (*y) = parseReal(token, default_y);
632   (*z) = parseReal(token, default_z);
633   (*w) = parseReal(token, default_w);
634 }
635 
636 static inline bool parseOnOff(const char **token, bool default_value = true) {
637   (*token) += strspn((*token), " \t");
638   const char *end = (*token) + strcspn((*token), " \t\r");
639 
640   bool ret = default_value;
641   if ((0 == strncmp((*token), "on", 2))) {
642     ret = true;
643   } else if ((0 == strncmp((*token), "off", 3))) {
644     ret = false;
645   }
646 
647   (*token) = end;
648   return ret;
649 }
650 
651 static inline texture_type_t parseTextureType(
652     const char **token, texture_type_t default_value = TEXTURE_TYPE_NONE) {
653   (*token) += strspn((*token), " \t");
654   const char *end = (*token) + strcspn((*token), " \t\r");
655   texture_type_t ty = default_value;
656 
657   if ((0 == strncmp((*token), "cube_top", strlen("cube_top")))) {
658     ty = TEXTURE_TYPE_CUBE_TOP;
659   } else if ((0 == strncmp((*token), "cube_bottom", strlen("cube_bottom")))) {
660     ty = TEXTURE_TYPE_CUBE_BOTTOM;
661   } else if ((0 == strncmp((*token), "cube_left", strlen("cube_left")))) {
662     ty = TEXTURE_TYPE_CUBE_LEFT;
663   } else if ((0 == strncmp((*token), "cube_right", strlen("cube_right")))) {
664     ty = TEXTURE_TYPE_CUBE_RIGHT;
665   } else if ((0 == strncmp((*token), "cube_front", strlen("cube_front")))) {
666     ty = TEXTURE_TYPE_CUBE_FRONT;
667   } else if ((0 == strncmp((*token), "cube_back", strlen("cube_back")))) {
668     ty = TEXTURE_TYPE_CUBE_BACK;
669   } else if ((0 == strncmp((*token), "sphere", strlen("sphere")))) {
670     ty = TEXTURE_TYPE_SPHERE;
671   }
672 
673   (*token) = end;
674   return ty;
675 }
676 
parseTagTriple(const char ** token)677 static tag_sizes parseTagTriple(const char **token) {
678   tag_sizes ts;
679 
680   ts.num_ints = atoi((*token));
681   (*token) += strcspn((*token), "/ \t\r");
682   if ((*token)[0] != '/') {
683     return ts;
684   }
685   (*token)++;
686 
687   ts.num_reals = atoi((*token));
688   (*token) += strcspn((*token), "/ \t\r");
689   if ((*token)[0] != '/') {
690     return ts;
691   }
692   (*token)++;
693 
694   ts.num_strings = atoi((*token));
695   (*token) += strcspn((*token), "/ \t\r") + 1;
696 
697   return ts;
698 }
699 
700 // Parse triples with index offsets: i, i/j/k, i//k, i/j
parseTriple(const char ** token,int vsize,int vnsize,int vtsize)701 static vertex_index parseTriple(const char **token, int vsize, int vnsize,
702                                 int vtsize) {
703   vertex_index vi(-1);
704 
705   vi.v_idx = fixIndex(atoi((*token)), vsize);
706   (*token) += strcspn((*token), "/ \t\r");
707   if ((*token)[0] != '/') {
708     return vi;
709   }
710   (*token)++;
711 
712   // i//k
713   if ((*token)[0] == '/') {
714     (*token)++;
715     vi.vn_idx = fixIndex(atoi((*token)), vnsize);
716     (*token) += strcspn((*token), "/ \t\r");
717     return vi;
718   }
719 
720   // i/j/k or i/j
721   vi.vt_idx = fixIndex(atoi((*token)), vtsize);
722   (*token) += strcspn((*token), "/ \t\r");
723   if ((*token)[0] != '/') {
724     return vi;
725   }
726 
727   // i/j/k
728   (*token)++;  // skip '/'
729   vi.vn_idx = fixIndex(atoi((*token)), vnsize);
730   (*token) += strcspn((*token), "/ \t\r");
731   return vi;
732 }
733 
734 // Parse raw triples: i, i/j/k, i//k, i/j
parseRawTriple(const char ** token)735 static vertex_index parseRawTriple(const char **token) {
736   vertex_index vi(static_cast<int>(0));  // 0 is an invalid index in OBJ
737 
738   vi.v_idx = atoi((*token));
739   (*token) += strcspn((*token), "/ \t\r");
740   if ((*token)[0] != '/') {
741     return vi;
742   }
743   (*token)++;
744 
745   // i//k
746   if ((*token)[0] == '/') {
747     (*token)++;
748     vi.vn_idx = atoi((*token));
749     (*token) += strcspn((*token), "/ \t\r");
750     return vi;
751   }
752 
753   // i/j/k or i/j
754   vi.vt_idx = atoi((*token));
755   (*token) += strcspn((*token), "/ \t\r");
756   if ((*token)[0] != '/') {
757     return vi;
758   }
759 
760   // i/j/k
761   (*token)++;  // skip '/'
762   vi.vn_idx = atoi((*token));
763   (*token) += strcspn((*token), "/ \t\r");
764   return vi;
765 }
766 
ParseTextureNameAndOption(std::string * texname,texture_option_t * texopt,const char * linebuf,const bool is_bump)767 static bool ParseTextureNameAndOption(std::string *texname,
768                                       texture_option_t *texopt,
769                                       const char *linebuf, const bool is_bump) {
770   // @todo { write more robust lexer and parser. }
771   bool found_texname = false;
772   std::string texture_name;
773 
774   // Fill with default value for texopt.
775   if (is_bump) {
776     texopt->imfchan = 'l';
777   } else {
778     texopt->imfchan = 'm';
779   }
780   texopt->bump_multiplier = 1.0f;
781   texopt->clamp = false;
782   texopt->blendu = true;
783   texopt->blendv = true;
784   texopt->sharpness = 1.0f;
785   texopt->brightness = 0.0f;
786   texopt->contrast = 1.0f;
787   texopt->origin_offset[0] = 0.0f;
788   texopt->origin_offset[1] = 0.0f;
789   texopt->origin_offset[2] = 0.0f;
790   texopt->scale[0] = 1.0f;
791   texopt->scale[1] = 1.0f;
792   texopt->scale[2] = 1.0f;
793   texopt->turbulence[0] = 0.0f;
794   texopt->turbulence[1] = 0.0f;
795   texopt->turbulence[2] = 0.0f;
796   texopt->type = TEXTURE_TYPE_NONE;
797 
798   const char *token = linebuf;  // Assume line ends with NULL
799 
800   while (!IS_NEW_LINE((*token))) {
801     token += strspn(token, " \t");  // skip space
802     if ((0 == strncmp(token, "-blendu", 7)) && IS_SPACE((token[7]))) {
803       token += 8;
804       texopt->blendu = parseOnOff(&token, /* default */ true);
805     } else if ((0 == strncmp(token, "-blendv", 7)) && IS_SPACE((token[7]))) {
806       token += 8;
807       texopt->blendv = parseOnOff(&token, /* default */ true);
808     } else if ((0 == strncmp(token, "-clamp", 6)) && IS_SPACE((token[6]))) {
809       token += 7;
810       texopt->clamp = parseOnOff(&token, /* default */ true);
811     } else if ((0 == strncmp(token, "-boost", 6)) && IS_SPACE((token[6]))) {
812       token += 7;
813       texopt->sharpness = parseReal(&token, 1.0);
814     } else if ((0 == strncmp(token, "-bm", 3)) && IS_SPACE((token[3]))) {
815       token += 4;
816       texopt->bump_multiplier = parseReal(&token, 1.0);
817     } else if ((0 == strncmp(token, "-o", 2)) && IS_SPACE((token[2]))) {
818       token += 3;
819       parseReal3(&(texopt->origin_offset[0]), &(texopt->origin_offset[1]),
820                   &(texopt->origin_offset[2]), &token);
821     } else if ((0 == strncmp(token, "-s", 2)) && IS_SPACE((token[2]))) {
822       token += 3;
823       parseReal3(&(texopt->scale[0]), &(texopt->scale[1]), &(texopt->scale[2]),
824                   &token, 1.0, 1.0, 1.0);
825     } else if ((0 == strncmp(token, "-t", 2)) && IS_SPACE((token[2]))) {
826       token += 3;
827       parseReal3(&(texopt->turbulence[0]), &(texopt->turbulence[1]),
828                   &(texopt->turbulence[2]), &token);
829     } else if ((0 == strncmp(token, "-type", 5)) && IS_SPACE((token[5]))) {
830       token += 5;
831       texopt->type = parseTextureType((&token), TEXTURE_TYPE_NONE);
832     } else if ((0 == strncmp(token, "-imfchan", 8)) && IS_SPACE((token[8]))) {
833       token += 9;
834       token += strspn(token, " \t");
835       const char *end = token + strcspn(token, " \t\r");
836       if ((end - token) == 1) {  // Assume one char for -imfchan
837         texopt->imfchan = (*token);
838       }
839       token = end;
840     } else if ((0 == strncmp(token, "-mm", 3)) && IS_SPACE((token[3]))) {
841       token += 4;
842       parseReal2(&(texopt->brightness), &(texopt->contrast), &token, 0.0, 1.0);
843     } else {
844       // Assume texture filename
845       size_t len = strcspn(token, " \t\r");  // untile next space
846       texture_name = std::string(token, token + len);
847       token += len;
848 
849       token += strspn(token, " \t");  // skip space
850 
851       found_texname = true;
852     }
853   }
854 
855   if (found_texname) {
856     (*texname) = texture_name;
857     return true;
858   } else {
859     return false;
860   }
861 }
862 
InitMaterial(material_t * material)863 static void InitMaterial(material_t *material) {
864   material->name = "";
865   material->ambient_texname = "";
866   material->diffuse_texname = "";
867   material->specular_texname = "";
868   material->specular_highlight_texname = "";
869   material->bump_texname = "";
870   material->displacement_texname = "";
871   material->reflection_texname = "";
872   material->alpha_texname = "";
873   for (int i = 0; i < 3; i++) {
874     material->ambient[i] = 0.f;
875     material->diffuse[i] = 0.f;
876     material->specular[i] = 0.f;
877     material->transmittance[i] = 0.f;
878     material->emission[i] = 0.f;
879   }
880   material->illum = 0;
881   material->dissolve = 1.f;
882   material->shininess = 1.f;
883   material->ior = 1.f;
884 
885   material->roughness = 0.f;
886   material->metallic = 0.f;
887   material->sheen = 0.f;
888   material->clearcoat_thickness = 0.f;
889   material->clearcoat_roughness = 0.f;
890   material->anisotropy_rotation = 0.f;
891   material->anisotropy = 0.f;
892   material->roughness_texname = "";
893   material->metallic_texname = "";
894   material->sheen_texname = "";
895   material->emissive_texname = "";
896   material->normal_texname = "";
897 
898   material->unknown_parameter.clear();
899 }
900 
exportFaceGroupToShape(shape_t * shape,const std::vector<std::vector<vertex_index>> & faceGroup,const std::vector<tag_t> & tags,const int material_id,const std::string & name,bool triangulate)901 static bool exportFaceGroupToShape(
902     shape_t *shape, const std::vector<std::vector<vertex_index> > &faceGroup,
903     const std::vector<tag_t> &tags, const int material_id,
904     const std::string &name, bool triangulate) {
905   if (faceGroup.empty()) {
906     return false;
907   }
908 
909   // Flatten vertices and indices
910   for (size_t i = 0; i < faceGroup.size(); i++) {
911     const std::vector<vertex_index> &face = faceGroup[i];
912 
913     vertex_index i0 = face[0];
914     vertex_index i1(-1);
915     vertex_index i2 = face[1];
916 
917     size_t npolys = face.size();
918 
919     if (triangulate) {
920       // Polygon -> triangle fan conversion
921       for (size_t k = 2; k < npolys; k++) {
922         i1 = i2;
923         i2 = face[k];
924 
925         index_t idx0, idx1, idx2;
926         idx0.vertex_index = i0.v_idx;
927         idx0.normal_index = i0.vn_idx;
928         idx0.texcoord_index = i0.vt_idx;
929         idx1.vertex_index = i1.v_idx;
930         idx1.normal_index = i1.vn_idx;
931         idx1.texcoord_index = i1.vt_idx;
932         idx2.vertex_index = i2.v_idx;
933         idx2.normal_index = i2.vn_idx;
934         idx2.texcoord_index = i2.vt_idx;
935 
936         shape->mesh.indices.push_back(idx0);
937         shape->mesh.indices.push_back(idx1);
938         shape->mesh.indices.push_back(idx2);
939 
940         shape->mesh.num_face_vertices.push_back(3);
941         shape->mesh.material_ids.push_back(material_id);
942       }
943     } else {
944       for (size_t k = 0; k < npolys; k++) {
945         index_t idx;
946         idx.vertex_index = face[k].v_idx;
947         idx.normal_index = face[k].vn_idx;
948         idx.texcoord_index = face[k].vt_idx;
949         shape->mesh.indices.push_back(idx);
950       }
951 
952       shape->mesh.num_face_vertices.push_back(
953           static_cast<unsigned char>(npolys));
954       shape->mesh.material_ids.push_back(material_id);  // per face
955     }
956   }
957 
958   shape->name = name;
959   shape->mesh.tags = tags;
960 
961   return true;
962 }
963 
964 // Split a string with specified delimiter character.
965 // http://stackoverflow.com/questions/236129/split-a-string-in-c
SplitString(const std::string & s,char delim,std::vector<std::string> & elems)966 static void SplitString(const std::string &s, char delim,
967                         std::vector<std::string> &elems) {
968   std::stringstream ss;
969   ss.str(s);
970   std::string item;
971   while (std::getline(ss, item, delim)) {
972     elems.push_back(item);
973   }
974 }
975 
LoadMtl(std::map<std::string,int> * material_map,std::vector<material_t> * materials,std::istream * inStream,std::string * warning)976 void LoadMtl(std::map<std::string, int> *material_map,
977              std::vector<material_t> *materials, std::istream *inStream,
978              std::string *warning) {
979   // Create a default material anyway.
980   material_t material;
981   InitMaterial(&material);
982 
983   // Issue 43. `d` wins against `Tr` since `Tr` is not in the MTL specification.
984   bool has_d = false;
985   bool has_tr = false;
986 
987   std::stringstream ss;
988 
989   std::string linebuf;
990   while (inStream->peek() != -1) {
991     safeGetline(*inStream, linebuf);
992 
993     // Trim trailing whitespace.
994     if (linebuf.size() > 0) {
995       linebuf = linebuf.substr(0, linebuf.find_last_not_of(" \t") + 1);
996     }
997 
998     // Trim newline '\r\n' or '\n'
999     if (linebuf.size() > 0) {
1000       if (linebuf[linebuf.size() - 1] == '\n')
1001         linebuf.erase(linebuf.size() - 1);
1002     }
1003     if (linebuf.size() > 0) {
1004       if (linebuf[linebuf.size() - 1] == '\r')
1005         linebuf.erase(linebuf.size() - 1);
1006     }
1007 
1008     // Skip if empty line.
1009     if (linebuf.empty()) {
1010       continue;
1011     }
1012 
1013     // Skip leading space.
1014     const char *token = linebuf.c_str();
1015     token += strspn(token, " \t");
1016 
1017     assert(token);
1018     if (token[0] == '\0') continue;  // empty line
1019 
1020     if (token[0] == '#') continue;  // comment line
1021 
1022     // new mtl
1023     if ((0 == strncmp(token, "newmtl", 6)) && IS_SPACE((token[6]))) {
1024       // flush previous material.
1025       if (!material.name.empty()) {
1026         material_map->insert(std::pair<std::string, int>(
1027             material.name, static_cast<int>(materials->size())));
1028         materials->push_back(material);
1029       }
1030 
1031       // initial temporary material
1032       InitMaterial(&material);
1033 
1034       has_d = false;
1035       has_tr = false;
1036 
1037       // set new mtl name
1038       token += 7;
1039       {
1040         std::stringstream sstr;
1041         sstr << token;
1042         material.name = sstr.str();
1043       }
1044       continue;
1045     }
1046 
1047     // ambient
1048     if (token[0] == 'K' && token[1] == 'a' && IS_SPACE((token[2]))) {
1049       token += 2;
1050       real_t r, g, b;
1051       parseReal3(&r, &g, &b, &token);
1052       material.ambient[0] = r;
1053       material.ambient[1] = g;
1054       material.ambient[2] = b;
1055       continue;
1056     }
1057 
1058     // diffuse
1059     if (token[0] == 'K' && token[1] == 'd' && IS_SPACE((token[2]))) {
1060       token += 2;
1061       real_t r, g, b;
1062       parseReal3(&r, &g, &b, &token);
1063       material.diffuse[0] = r;
1064       material.diffuse[1] = g;
1065       material.diffuse[2] = b;
1066       continue;
1067     }
1068 
1069     // specular
1070     if (token[0] == 'K' && token[1] == 's' && IS_SPACE((token[2]))) {
1071       token += 2;
1072       real_t r, g, b;
1073       parseReal3(&r, &g, &b, &token);
1074       material.specular[0] = r;
1075       material.specular[1] = g;
1076       material.specular[2] = b;
1077       continue;
1078     }
1079 
1080     // transmittance
1081     if ((token[0] == 'K' && token[1] == 't' && IS_SPACE((token[2]))) ||
1082         (token[0] == 'T' && token[1] == 'f' && IS_SPACE((token[2])))) {
1083       token += 2;
1084       real_t r, g, b;
1085       parseReal3(&r, &g, &b, &token);
1086       material.transmittance[0] = r;
1087       material.transmittance[1] = g;
1088       material.transmittance[2] = b;
1089       continue;
1090     }
1091 
1092     // ior(index of refraction)
1093     if (token[0] == 'N' && token[1] == 'i' && IS_SPACE((token[2]))) {
1094       token += 2;
1095       material.ior = parseReal(&token);
1096       continue;
1097     }
1098 
1099     // emission
1100     if (token[0] == 'K' && token[1] == 'e' && IS_SPACE(token[2])) {
1101       token += 2;
1102       real_t r, g, b;
1103       parseReal3(&r, &g, &b, &token);
1104       material.emission[0] = r;
1105       material.emission[1] = g;
1106       material.emission[2] = b;
1107       continue;
1108     }
1109 
1110     // shininess
1111     if (token[0] == 'N' && token[1] == 's' && IS_SPACE(token[2])) {
1112       token += 2;
1113       material.shininess = parseReal(&token);
1114       continue;
1115     }
1116 
1117     // illum model
1118     if (0 == strncmp(token, "illum", 5) && IS_SPACE(token[5])) {
1119       token += 6;
1120       material.illum = parseInt(&token);
1121       continue;
1122     }
1123 
1124     // dissolve
1125     if ((token[0] == 'd' && IS_SPACE(token[1]))) {
1126       token += 1;
1127       material.dissolve = parseReal(&token);
1128 
1129       if (has_tr) {
1130         ss << "WARN: Both `d` and `Tr` parameters defined for \""
1131            << material.name << "\". Use the value of `d` for dissolve."
1132            << std::endl;
1133       }
1134       has_d = true;
1135       continue;
1136     }
1137     if (token[0] == 'T' && token[1] == 'r' && IS_SPACE(token[2])) {
1138       token += 2;
1139       if (has_d) {
1140         // `d` wins. Ignore `Tr` value.
1141         ss << "WARN: Both `d` and `Tr` parameters defined for \""
1142            << material.name << "\". Use the value of `d` for dissolve."
1143            << std::endl;
1144       } else {
1145         // We invert value of Tr(assume Tr is in range [0, 1])
1146         // NOTE: Interpretation of Tr is application(exporter) dependent. For
1147         // some application(e.g. 3ds max obj exporter), Tr = d(Issue 43)
1148         material.dissolve = 1.0f - parseReal(&token);
1149       }
1150       has_tr = true;
1151       continue;
1152     }
1153 
1154     // PBR: roughness
1155     if (token[0] == 'P' && token[1] == 'r' && IS_SPACE(token[2])) {
1156       token += 2;
1157       material.roughness = parseReal(&token);
1158       continue;
1159     }
1160 
1161     // PBR: metallic
1162     if (token[0] == 'P' && token[1] == 'm' && IS_SPACE(token[2])) {
1163       token += 2;
1164       material.metallic = parseReal(&token);
1165       continue;
1166     }
1167 
1168     // PBR: sheen
1169     if (token[0] == 'P' && token[1] == 's' && IS_SPACE(token[2])) {
1170       token += 2;
1171       material.sheen = parseReal(&token);
1172       continue;
1173     }
1174 
1175     // PBR: clearcoat thickness
1176     if (token[0] == 'P' && token[1] == 'c' && IS_SPACE(token[2])) {
1177       token += 2;
1178       material.clearcoat_thickness = parseReal(&token);
1179       continue;
1180     }
1181 
1182     // PBR: clearcoat roughness
1183     if ((0 == strncmp(token, "Pcr", 3)) && IS_SPACE(token[3])) {
1184       token += 4;
1185       material.clearcoat_roughness = parseReal(&token);
1186       continue;
1187     }
1188 
1189     // PBR: anisotropy
1190     if ((0 == strncmp(token, "aniso", 5)) && IS_SPACE(token[5])) {
1191       token += 6;
1192       material.anisotropy = parseReal(&token);
1193       continue;
1194     }
1195 
1196     // PBR: anisotropy rotation
1197     if ((0 == strncmp(token, "anisor", 6)) && IS_SPACE(token[6])) {
1198       token += 7;
1199       material.anisotropy_rotation = parseReal(&token);
1200       continue;
1201     }
1202 
1203     // ambient texture
1204     if ((0 == strncmp(token, "map_Ka", 6)) && IS_SPACE(token[6])) {
1205       token += 7;
1206       ParseTextureNameAndOption(&(material.ambient_texname),
1207                                 &(material.ambient_texopt), token,
1208                                 /* is_bump */ false);
1209       continue;
1210     }
1211 
1212     // diffuse texture
1213     if ((0 == strncmp(token, "map_Kd", 6)) && IS_SPACE(token[6])) {
1214       token += 7;
1215       ParseTextureNameAndOption(&(material.diffuse_texname),
1216                                 &(material.diffuse_texopt), token,
1217                                 /* is_bump */ false);
1218       continue;
1219     }
1220 
1221     // specular texture
1222     if ((0 == strncmp(token, "map_Ks", 6)) && IS_SPACE(token[6])) {
1223       token += 7;
1224       ParseTextureNameAndOption(&(material.specular_texname),
1225                                 &(material.specular_texopt), token,
1226                                 /* is_bump */ false);
1227       continue;
1228     }
1229 
1230     // specular highlight texture
1231     if ((0 == strncmp(token, "map_Ns", 6)) && IS_SPACE(token[6])) {
1232       token += 7;
1233       ParseTextureNameAndOption(&(material.specular_highlight_texname),
1234                                 &(material.specular_highlight_texopt), token,
1235                                 /* is_bump */ false);
1236       continue;
1237     }
1238 
1239     // bump texture
1240     if ((0 == strncmp(token, "map_bump", 8)) && IS_SPACE(token[8])) {
1241       token += 9;
1242       ParseTextureNameAndOption(&(material.bump_texname),
1243                                 &(material.bump_texopt), token,
1244                                 /* is_bump */ true);
1245       continue;
1246     }
1247 
1248     // bump texture
1249     if ((0 == strncmp(token, "bump", 4)) && IS_SPACE(token[4])) {
1250       token += 5;
1251       ParseTextureNameAndOption(&(material.bump_texname),
1252                                 &(material.bump_texopt), token,
1253                                 /* is_bump */ true);
1254       continue;
1255     }
1256 
1257     // alpha texture
1258     if ((0 == strncmp(token, "map_d", 5)) && IS_SPACE(token[5])) {
1259       token += 6;
1260       material.alpha_texname = token;
1261       ParseTextureNameAndOption(&(material.alpha_texname),
1262                                 &(material.alpha_texopt), token,
1263                                 /* is_bump */ false);
1264       continue;
1265     }
1266 
1267     // displacement texture
1268     if ((0 == strncmp(token, "disp", 4)) && IS_SPACE(token[4])) {
1269       token += 5;
1270       ParseTextureNameAndOption(&(material.displacement_texname),
1271                                 &(material.displacement_texopt), token,
1272                                 /* is_bump */ false);
1273       continue;
1274     }
1275 
1276     // reflection map
1277     if ((0 == strncmp(token, "refl", 4)) && IS_SPACE(token[4])) {
1278       token += 5;
1279       ParseTextureNameAndOption(&(material.reflection_texname),
1280                                 &(material.reflection_texopt), token,
1281                                 /* is_bump */ false);
1282       continue;
1283     }
1284 
1285     // PBR: roughness texture
1286     if ((0 == strncmp(token, "map_Pr", 6)) && IS_SPACE(token[6])) {
1287       token += 7;
1288       ParseTextureNameAndOption(&(material.roughness_texname),
1289                                 &(material.roughness_texopt), token,
1290                                 /* is_bump */ false);
1291       continue;
1292     }
1293 
1294     // PBR: metallic texture
1295     if ((0 == strncmp(token, "map_Pm", 6)) && IS_SPACE(token[6])) {
1296       token += 7;
1297       ParseTextureNameAndOption(&(material.metallic_texname),
1298                                 &(material.metallic_texopt), token,
1299                                 /* is_bump */ false);
1300       continue;
1301     }
1302 
1303     // PBR: sheen texture
1304     if ((0 == strncmp(token, "map_Ps", 6)) && IS_SPACE(token[6])) {
1305       token += 7;
1306       ParseTextureNameAndOption(&(material.sheen_texname),
1307                                 &(material.sheen_texopt), token,
1308                                 /* is_bump */ false);
1309       continue;
1310     }
1311 
1312     // PBR: emissive texture
1313     if ((0 == strncmp(token, "map_Ke", 6)) && IS_SPACE(token[6])) {
1314       token += 7;
1315       ParseTextureNameAndOption(&(material.emissive_texname),
1316                                 &(material.emissive_texopt), token,
1317                                 /* is_bump */ false);
1318       continue;
1319     }
1320 
1321     // PBR: normal map texture
1322     if ((0 == strncmp(token, "norm", 4)) && IS_SPACE(token[4])) {
1323       token += 5;
1324       ParseTextureNameAndOption(
1325           &(material.normal_texname), &(material.normal_texopt), token,
1326           /* is_bump */ false);  // @fixme { is_bump will be true? }
1327       continue;
1328     }
1329 
1330     // unknown parameter
1331     const char *_space = strchr(token, ' ');
1332     if (!_space) {
1333       _space = strchr(token, '\t');
1334     }
1335     if (_space) {
1336       std::ptrdiff_t len = _space - token;
1337       std::string key(token, static_cast<size_t>(len));
1338       std::string value = _space + 1;
1339       material.unknown_parameter.insert(
1340           std::pair<std::string, std::string>(key, value));
1341     }
1342   }
1343   // flush last material.
1344   material_map->insert(std::pair<std::string, int>(
1345       material.name, static_cast<int>(materials->size())));
1346   materials->push_back(material);
1347 
1348   if (warning) {
1349     (*warning) = ss.str();
1350   }
1351 }
1352 
operator()1353 bool MaterialFileReader::operator()(const std::string &matId,
1354                                     std::vector<material_t> *materials,
1355                                     std::map<std::string, int> *matMap,
1356                                     std::string *err) {
1357   std::string filepath;
1358 
1359   if (!m_mtlBaseDir.empty()) {
1360     filepath = std::string(m_mtlBaseDir) + matId;
1361   } else {
1362     filepath = matId;
1363   }
1364 
1365   std::ifstream matIStream(filepath.c_str());
1366   if (!matIStream) {
1367     std::stringstream ss;
1368     ss << "WARN: Material file [ " << filepath << " ] not found." << std::endl;
1369     if (err) {
1370       (*err) += ss.str();
1371     }
1372     return false;
1373   }
1374 
1375   std::string warning;
1376   LoadMtl(matMap, materials, &matIStream, &warning);
1377 
1378   if (!warning.empty()) {
1379     if (err) {
1380       (*err) += warning;
1381     }
1382   }
1383 
1384   return true;
1385 }
1386 
operator()1387 bool MaterialStreamReader::operator()(const std::string &matId,
1388                                       std::vector<material_t> *materials,
1389                                       std::map<std::string, int> *matMap,
1390                                       std::string *err) {
1391   (void)matId;
1392   if (!m_inStream) {
1393     std::stringstream ss;
1394     ss << "WARN: Material stream in error state. " << std::endl;
1395     if (err) {
1396       (*err) += ss.str();
1397     }
1398     return false;
1399   }
1400 
1401   std::string warning;
1402   LoadMtl(matMap, materials, &m_inStream, &warning);
1403 
1404   if (!warning.empty()) {
1405     if (err) {
1406       (*err) += warning;
1407     }
1408   }
1409 
1410   return true;
1411 }
1412 
LoadObj(attrib_t * attrib,std::vector<shape_t> * shapes,std::vector<material_t> * materials,std::string * err,const char * filename,const char * mtl_basedir,bool trianglulate)1413 bool LoadObj(attrib_t *attrib, std::vector<shape_t> *shapes,
1414              std::vector<material_t> *materials, std::string *err,
1415              const char *filename, const char *mtl_basedir, bool trianglulate) {
1416   attrib->vertices.clear();
1417   attrib->normals.clear();
1418   attrib->texcoords.clear();
1419   shapes->clear();
1420 
1421   std::stringstream errss;
1422 
1423   std::ifstream ifs(filename);
1424   if (!ifs) {
1425     errss << "Cannot open file [" << filename << "]" << std::endl;
1426     if (err) {
1427       (*err) = errss.str();
1428     }
1429     return false;
1430   }
1431 
1432   std::string baseDir;
1433   if (mtl_basedir) {
1434     baseDir = mtl_basedir;
1435   }
1436   MaterialFileReader matFileReader(baseDir);
1437 
1438   return LoadObj(attrib, shapes, materials, err, &ifs, &matFileReader,
1439                  trianglulate);
1440 }
1441 
LoadObj(attrib_t * attrib,std::vector<shape_t> * shapes,std::vector<material_t> * materials,std::string * err,std::istream * inStream,MaterialReader * readMatFn,bool triangulate)1442 bool LoadObj(attrib_t *attrib, std::vector<shape_t> *shapes,
1443              std::vector<material_t> *materials, std::string *err,
1444              std::istream *inStream, MaterialReader *readMatFn /*= NULL*/,
1445              bool triangulate) {
1446   std::stringstream errss;
1447 
1448   std::vector<real_t> v;
1449   std::vector<real_t> vn;
1450   std::vector<real_t> vt;
1451   std::vector<tag_t> tags;
1452   std::vector<std::vector<vertex_index> > faceGroup;
1453   std::string name;
1454 
1455   // material
1456   std::map<std::string, int> material_map;
1457   int material = -1;
1458 
1459   shape_t shape;
1460 
1461   std::string linebuf;
1462   while (inStream->peek() != -1) {
1463     safeGetline(*inStream, linebuf);
1464 
1465     // Trim newline '\r\n' or '\n'
1466     if (linebuf.size() > 0) {
1467       if (linebuf[linebuf.size() - 1] == '\n')
1468         linebuf.erase(linebuf.size() - 1);
1469     }
1470     if (linebuf.size() > 0) {
1471       if (linebuf[linebuf.size() - 1] == '\r')
1472         linebuf.erase(linebuf.size() - 1);
1473     }
1474 
1475     // Skip if empty line.
1476     if (linebuf.empty()) {
1477       continue;
1478     }
1479 
1480     // Skip leading space.
1481     const char *token = linebuf.c_str();
1482     token += strspn(token, " \t");
1483 
1484     assert(token);
1485     if (token[0] == '\0') continue;  // empty line
1486 
1487     if (token[0] == '#') continue;  // comment line
1488 
1489     // vertex
1490     if (token[0] == 'v' && IS_SPACE((token[1]))) {
1491       token += 2;
1492       real_t x, y, z;
1493       parseReal3(&x, &y, &z, &token);
1494       v.push_back(x);
1495       v.push_back(y);
1496       v.push_back(z);
1497       continue;
1498     }
1499 
1500     // normal
1501     if (token[0] == 'v' && token[1] == 'n' && IS_SPACE((token[2]))) {
1502       token += 3;
1503       real_t x, y, z;
1504       parseReal3(&x, &y, &z, &token);
1505       vn.push_back(x);
1506       vn.push_back(y);
1507       vn.push_back(z);
1508       continue;
1509     }
1510 
1511     // texcoord
1512     if (token[0] == 'v' && token[1] == 't' && IS_SPACE((token[2]))) {
1513       token += 3;
1514       real_t x, y;
1515       parseReal2(&x, &y, &token);
1516       vt.push_back(x);
1517       vt.push_back(y);
1518       continue;
1519     }
1520 
1521     // face
1522     if (token[0] == 'f' && IS_SPACE((token[1]))) {
1523       token += 2;
1524       token += strspn(token, " \t");
1525 
1526       std::vector<vertex_index> face;
1527       face.reserve(3);
1528 
1529       while (!IS_NEW_LINE(token[0])) {
1530         vertex_index vi = parseTriple(&token, static_cast<int>(v.size() / 3),
1531                                       static_cast<int>(vn.size() / 3),
1532                                       static_cast<int>(vt.size() / 2));
1533         face.push_back(vi);
1534         size_t n = strspn(token, " \t\r");
1535         token += n;
1536       }
1537 
1538       faceGroup.emplace_back(std::move(std::vector<vertex_index>()));
1539       faceGroup[faceGroup.size() - 1].swap(face);
1540 
1541       continue;
1542     }
1543 
1544     // use mtl
1545     if ((0 == strncmp(token, "usemtl", 6)) && IS_SPACE((token[6]))) {
1546       token += 7;
1547       std::stringstream ss;
1548       ss << token;
1549       std::string namebuf = ss.str();
1550 
1551       int newMaterialId = -1;
1552       if (material_map.find(namebuf) != material_map.end()) {
1553         newMaterialId = material_map[namebuf];
1554       } else {
1555         // { error!! material not found }
1556       }
1557 
1558       if (newMaterialId != material) {
1559         // Create per-face material. Thus we don't add `shape` to `shapes` at
1560         // this time.
1561         // just clear `faceGroup` after `exportFaceGroupToShape()` call.
1562         exportFaceGroupToShape(&shape, faceGroup, tags, material, name,
1563                                triangulate);
1564         faceGroup.clear();
1565         material = newMaterialId;
1566       }
1567 
1568       continue;
1569     }
1570 
1571     // load mtl
1572     if ((0 == strncmp(token, "mtllib", 6)) && IS_SPACE((token[6]))) {
1573       if (readMatFn) {
1574         token += 7;
1575 
1576         std::vector<std::string> filenames;
1577         SplitString(std::string(token), ' ', filenames);
1578 
1579         if (filenames.empty()) {
1580           if (err) {
1581             (*err) +=
1582                 "WARN: Looks like empty filename for mtllib. Use default "
1583                 "material. \n";
1584           }
1585         } else {
1586           bool found = false;
1587           for (size_t s = 0; s < filenames.size(); s++) {
1588             std::string err_mtl;
1589             bool ok = (*readMatFn)(filenames[s].c_str(), materials,
1590                                    &material_map, &err_mtl);
1591             if (err && (!err_mtl.empty())) {
1592               (*err) += err_mtl;  // This should be warn message.
1593             }
1594 
1595             if (ok) {
1596               found = true;
1597               break;
1598             }
1599           }
1600 
1601           if (!found) {
1602             if (err) {
1603               (*err) +=
1604                   "WARN: Failed to load material file(s). Use default "
1605                   "material.\n";
1606             }
1607           }
1608         }
1609       }
1610 
1611       continue;
1612     }
1613 
1614     // group name
1615     if (token[0] == 'g' && IS_SPACE((token[1]))) {
1616       // flush previous face group.
1617       bool ret = exportFaceGroupToShape(&shape, faceGroup, tags, material, name,
1618                                         triangulate);
1619       if (ret) {
1620         shapes->push_back(shape);
1621       }
1622 
1623       shape = shape_t();
1624 
1625       // material = -1;
1626       faceGroup.clear();
1627 
1628       std::vector<std::string> names;
1629       names.reserve(2);
1630 
1631       while (!IS_NEW_LINE(token[0])) {
1632         std::string str = parseString(&token);
1633         names.push_back(str);
1634         token += strspn(token, " \t\r");  // skip tag
1635       }
1636 
1637       assert(names.size() > 0);
1638 
1639       // names[0] must be 'g', so skip the 0th element.
1640       if (names.size() > 1) {
1641         name = names[1];
1642       } else {
1643         name = "";
1644       }
1645 
1646       continue;
1647     }
1648 
1649     // object name
1650     if (token[0] == 'o' && IS_SPACE((token[1]))) {
1651       // flush previous face group.
1652       bool ret = exportFaceGroupToShape(&shape, faceGroup, tags, material, name,
1653                                         triangulate);
1654       if (ret) {
1655         shapes->push_back(shape);
1656       }
1657 
1658       // material = -1;
1659       faceGroup.clear();
1660       shape = shape_t();
1661 
1662       // @todo { multiple object name? }
1663       token += 2;
1664       std::stringstream ss;
1665       ss << token;
1666       name = ss.str();
1667 
1668       continue;
1669     }
1670 
1671     if (token[0] == 't' && IS_SPACE(token[1])) {
1672       tag_t tag;
1673 
1674       token += 2;
1675       std::stringstream ss;
1676       ss << token;
1677       tag.name = ss.str();
1678 
1679       token += tag.name.size() + 1;
1680 
1681       tag_sizes ts = parseTagTriple(&token);
1682 
1683       tag.intValues.resize(static_cast<size_t>(ts.num_ints));
1684 
1685       for (size_t i = 0; i < static_cast<size_t>(ts.num_ints); ++i) {
1686         tag.intValues[i] = atoi(token);
1687         token += strcspn(token, "/ \t\r") + 1;
1688       }
1689 
1690       tag.floatValues.resize(static_cast<size_t>(ts.num_reals));
1691       for (size_t i = 0; i < static_cast<size_t>(ts.num_reals); ++i) {
1692         tag.floatValues[i] = parseReal(&token);
1693         token += strcspn(token, "/ \t\r") + 1;
1694       }
1695 
1696       tag.stringValues.resize(static_cast<size_t>(ts.num_strings));
1697       for (size_t i = 0; i < static_cast<size_t>(ts.num_strings); ++i) {
1698         std::stringstream sstr;
1699         sstr << token;
1700         tag.stringValues[i] = sstr.str();
1701         token += tag.stringValues[i].size() + 1;
1702       }
1703 
1704       tags.push_back(tag);
1705     }
1706 
1707     // Ignore unknown command.
1708   }
1709 
1710   bool ret = exportFaceGroupToShape(&shape, faceGroup, tags, material, name,
1711                                     triangulate);
1712   // exportFaceGroupToShape return false when `usemtl` is called in the last
1713   // line.
1714   // we also add `shape` to `shapes` when `shape.mesh` has already some
1715   // faces(indices)
1716   if (ret || shape.mesh.indices.size()) {
1717     shapes->push_back(shape);
1718   }
1719   faceGroup.clear();  // for safety
1720 
1721   if (err) {
1722     (*err) += errss.str();
1723   }
1724 
1725   attrib->vertices.swap(v);
1726   attrib->normals.swap(vn);
1727   attrib->texcoords.swap(vt);
1728 
1729   return true;
1730 }
1731 
LoadObjWithCallback(std::istream & inStream,const callback_t & callback,void * user_data,MaterialReader * readMatFn,std::string * err)1732 bool LoadObjWithCallback(std::istream &inStream, const callback_t &callback,
1733                          void *user_data /*= NULL*/,
1734                          MaterialReader *readMatFn /*= NULL*/,
1735                          std::string *err /*= NULL*/) {
1736   std::stringstream errss;
1737 
1738   // material
1739   std::map<std::string, int> material_map;
1740   int material_id = -1;  // -1 = invalid
1741 
1742   std::vector<index_t> indices;
1743   std::vector<material_t> materials;
1744   std::vector<std::string> names;
1745   names.reserve(2);
1746   std::string name;
1747   std::vector<const char *> names_out;
1748 
1749   std::string linebuf;
1750   while (inStream.peek() != -1) {
1751     safeGetline(inStream, linebuf);
1752 
1753     // Trim newline '\r\n' or '\n'
1754     if (linebuf.size() > 0) {
1755       if (linebuf[linebuf.size() - 1] == '\n')
1756         linebuf.erase(linebuf.size() - 1);
1757     }
1758     if (linebuf.size() > 0) {
1759       if (linebuf[linebuf.size() - 1] == '\r')
1760         linebuf.erase(linebuf.size() - 1);
1761     }
1762 
1763     // Skip if empty line.
1764     if (linebuf.empty()) {
1765       continue;
1766     }
1767 
1768     // Skip leading space.
1769     const char *token = linebuf.c_str();
1770     token += strspn(token, " \t");
1771 
1772     assert(token);
1773     if (token[0] == '\0') continue;  // empty line
1774 
1775     if (token[0] == '#') continue;  // comment line
1776 
1777     // vertex
1778     if (token[0] == 'v' && IS_SPACE((token[1]))) {
1779       token += 2;
1780       real_t x, y, z, w;  // w is optional. default = 1.0
1781       parseV(&x, &y, &z, &w, &token);
1782       if (callback.vertex_cb) {
1783         callback.vertex_cb(user_data, x, y, z, w);
1784       }
1785       continue;
1786     }
1787 
1788     // normal
1789     if (token[0] == 'v' && token[1] == 'n' && IS_SPACE((token[2]))) {
1790       token += 3;
1791       real_t x, y, z;
1792       parseReal3(&x, &y, &z, &token);
1793       if (callback.normal_cb) {
1794         callback.normal_cb(user_data, x, y, z);
1795       }
1796       continue;
1797     }
1798 
1799     // texcoord
1800     if (token[0] == 'v' && token[1] == 't' && IS_SPACE((token[2]))) {
1801       token += 3;
1802       real_t x, y, z;  // y and z are optional. default = 0.0
1803       parseReal3(&x, &y, &z, &token);
1804       if (callback.texcoord_cb) {
1805         callback.texcoord_cb(user_data, x, y, z);
1806       }
1807       continue;
1808     }
1809 
1810     // face
1811     if (token[0] == 'f' && IS_SPACE((token[1]))) {
1812       token += 2;
1813       token += strspn(token, " \t");
1814 
1815       indices.clear();
1816       while (!IS_NEW_LINE(token[0])) {
1817         vertex_index vi = parseRawTriple(&token);
1818 
1819         index_t idx;
1820         idx.vertex_index = vi.v_idx;
1821         idx.normal_index = vi.vn_idx;
1822         idx.texcoord_index = vi.vt_idx;
1823 
1824         indices.push_back(idx);
1825         size_t n = strspn(token, " \t\r");
1826         token += n;
1827       }
1828 
1829       if (callback.index_cb && indices.size() > 0) {
1830         callback.index_cb(user_data, &indices.at(0),
1831                           static_cast<int>(indices.size()));
1832       }
1833 
1834       continue;
1835     }
1836 
1837     // use mtl
1838     if ((0 == strncmp(token, "usemtl", 6)) && IS_SPACE((token[6]))) {
1839       token += 7;
1840       std::stringstream ss;
1841       ss << token;
1842       std::string namebuf = ss.str();
1843 
1844       int newMaterialId = -1;
1845       if (material_map.find(namebuf) != material_map.end()) {
1846         newMaterialId = material_map[namebuf];
1847       } else {
1848         // { error!! material not found }
1849       }
1850 
1851       if (newMaterialId != material_id) {
1852         material_id = newMaterialId;
1853       }
1854 
1855       if (callback.usemtl_cb) {
1856         callback.usemtl_cb(user_data, namebuf.c_str(), material_id);
1857       }
1858 
1859       continue;
1860     }
1861 
1862     // load mtl
1863     if ((0 == strncmp(token, "mtllib", 6)) && IS_SPACE((token[6]))) {
1864       if (readMatFn) {
1865         token += 7;
1866 
1867         std::vector<std::string> filenames;
1868         SplitString(std::string(token), ' ', filenames);
1869 
1870         if (filenames.empty()) {
1871           if (err) {
1872             (*err) +=
1873                 "WARN: Looks like empty filename for mtllib. Use default "
1874                 "material. \n";
1875           }
1876         } else {
1877           bool found = false;
1878           for (size_t s = 0; s < filenames.size(); s++) {
1879             std::string err_mtl;
1880             bool ok = (*readMatFn)(filenames[s].c_str(), &materials,
1881                                    &material_map, &err_mtl);
1882             if (err && (!err_mtl.empty())) {
1883               (*err) += err_mtl;  // This should be warn message.
1884             }
1885 
1886             if (ok) {
1887               found = true;
1888               break;
1889             }
1890           }
1891 
1892           if (!found) {
1893             if (err) {
1894               (*err) +=
1895                   "WARN: Failed to load material file(s). Use default "
1896                   "material.\n";
1897             }
1898           } else {
1899             if (callback.mtllib_cb) {
1900               callback.mtllib_cb(user_data, &materials.at(0),
1901                                  static_cast<int>(materials.size()));
1902             }
1903           }
1904         }
1905       }
1906 
1907       continue;
1908     }
1909 
1910     // group name
1911     if (token[0] == 'g' && IS_SPACE((token[1]))) {
1912       names.clear();
1913 
1914       while (!IS_NEW_LINE(token[0])) {
1915         std::string str = parseString(&token);
1916         names.push_back(str);
1917         token += strspn(token, " \t\r");  // skip tag
1918       }
1919 
1920       assert(names.size() > 0);
1921 
1922       // names[0] must be 'g', so skip the 0th element.
1923       if (names.size() > 1) {
1924         name = names[1];
1925       } else {
1926         name.clear();
1927       }
1928 
1929       if (callback.group_cb) {
1930         if (names.size() > 1) {
1931           // create const char* array.
1932           names_out.resize(names.size() - 1);
1933           for (size_t j = 0; j < names_out.size(); j++) {
1934             names_out[j] = names[j + 1].c_str();
1935           }
1936           callback.group_cb(user_data, &names_out.at(0),
1937                             static_cast<int>(names_out.size()));
1938 
1939         } else {
1940           callback.group_cb(user_data, NULL, 0);
1941         }
1942       }
1943 
1944       continue;
1945     }
1946 
1947     // object name
1948     if (token[0] == 'o' && IS_SPACE((token[1]))) {
1949       // @todo { multiple object name? }
1950       token += 2;
1951 
1952       std::stringstream ss;
1953       ss << token;
1954       std::string object_name = ss.str();
1955 
1956       if (callback.object_cb) {
1957         callback.object_cb(user_data, object_name.c_str());
1958       }
1959 
1960       continue;
1961     }
1962 
1963 #if 0  // @todo
1964     if (token[0] == 't' && IS_SPACE(token[1])) {
1965       tag_t tag;
1966 
1967       token += 2;
1968       std::stringstream ss;
1969       ss << token;
1970       tag.name = ss.str();
1971 
1972       token += tag.name.size() + 1;
1973 
1974       tag_sizes ts = parseTagTriple(&token);
1975 
1976       tag.intValues.resize(static_cast<size_t>(ts.num_ints));
1977 
1978       for (size_t i = 0; i < static_cast<size_t>(ts.num_ints); ++i) {
1979         tag.intValues[i] = atoi(token);
1980         token += strcspn(token, "/ \t\r") + 1;
1981       }
1982 
1983       tag.floatValues.resize(static_cast<size_t>(ts.num_reals));
1984       for (size_t i = 0; i < static_cast<size_t>(ts.num_reals); ++i) {
1985         tag.floatValues[i] = parseReal(&token);
1986         token += strcspn(token, "/ \t\r") + 1;
1987       }
1988 
1989       tag.stringValues.resize(static_cast<size_t>(ts.num_strings));
1990       for (size_t i = 0; i < static_cast<size_t>(ts.num_strings); ++i) {
1991         std::stringstream ss;
1992         ss << token;
1993         tag.stringValues[i] = ss.str();
1994         token += tag.stringValues[i].size() + 1;
1995       }
1996 
1997       tags.push_back(tag);
1998     }
1999 #endif
2000 
2001     // Ignore unknown command.
2002   }
2003 
2004   if (err) {
2005     (*err) += errss.str();
2006   }
2007 
2008   return true;
2009 }
2010 }  // namespace tinyobj
2011 }  // namespace ignition
2012 
2013 #endif
2014