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