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