Coverage Report

Created: 2025-11-24 06:29

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