Coverage Report

Created: 2026-02-13 06:06

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
49
  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
43
  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
43
      : 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
49
      : 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
49
  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
43
MaterialReader::~MaterialReader() {}
696
697
struct vertex_index_t {
698
  int v_idx, vt_idx, vn_idx;
699
75
  vertex_index_t() : v_idx(-1), vt_idx(-1), vn_idx(-1) {}
700
73
  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
18
  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
29
  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
48
  void clear() {
753
48
    faceGroup.clear();
754
48
    lineGroup.clear();
755
48
    pointsGroup.clear();
756
48
  }
757
758
63
  bool IsEmpty() const {
759
63
    return faceGroup.empty() && lineGroup.empty() && pointsGroup.empty();
760
63
  }
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
89.5k
static std::istream &safeGetline(std::istream &is, std::string &t) {
768
89.5k
  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
89.5k
  std::istream::sentry se(is, true);
777
89.5k
  std::streambuf *sb = is.rdbuf();
778
779
89.5k
  if (se) {
780
40.5M
    for (;;) {
781
40.5M
      int c = sb->sbumpc();
782
40.5M
      switch (c) {
783
80.7k
        case '\n':
784
80.7k
          return is;
785
8.75k
        case '\r':
786
8.75k
          if (sb->sgetc() == '\n') sb->sbumpc();
787
8.75k
          return is;
788
71
        case EOF:
789
          // Also handle the case when the last line has no line ending
790
71
          if (t.empty()) is.setstate(std::ios::eofbit);
791
71
          return is;
792
40.4M
        default:
793
40.4M
          t += static_cast<char>(c);
794
40.5M
      }
795
40.5M
    }
796
89.5k
  }
797
798
0
  return is;
799
89.5k
}
800
801
68.6k
#define IS_SPACE(x) (((x) == ' ') || ((x) == '\t'))
802
#define IS_DIGIT(x) \
803
1.80k
  (static_cast<unsigned int>((x) - '0') < static_cast<unsigned int>(10))
804
3.13k
#define IS_NEW_LINE(x) (((x) == '\r') || ((x) == '\n') || ((x) == '\0'))
805
806
template <typename T>
807
0
static inline std::string toString(const T &t) {
808
0
  std::stringstream ss;
809
0
  ss << t;
810
0
  return ss.str();
811
0
}
812
813
72
static inline std::string removeUtf8Bom(const std::string& input) {
814
    // UTF-8 BOM = 0xEF,0xBB,0xBF
815
72
    if (input.size() >= 3 &&
816
67
        static_cast<unsigned char>(input[0]) == 0xEF &&
817
10
        static_cast<unsigned char>(input[1]) == 0xBB &&
818
10
        static_cast<unsigned char>(input[2]) == 0xBF) {
819
10
        return input.substr(3); // Skip BOM
820
10
    }
821
62
    return input;
822
72
}
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
73
                            const warning_context &context) {
832
73
  if (!ret) {
833
0
    return false;
834
0
  }
835
836
73
  if (idx > 0) {
837
73
    (*ret) = idx - 1;
838
73
    return true;
839
73
  }
840
841
0
  if (idx == 0) {
842
    // zero is not allowed according to the spec.
843
0
    if (context.warn) {
844
0
      (*context.warn) +=
845
0
          "A zero value index found (will have a value of -1 for normal and "
846
0
          "tex indices. Line " +
847
0
          toString(context.line_number) + ").\n";
848
0
    }
849
850
0
    (*ret) = idx - 1;
851
0
    return allow_zero;
852
0
  }
853
854
0
  if (idx < 0) {
855
0
    (*ret) = n + idx;  // negative value = relative
856
0
    if ((*ret) < 0) {
857
0
      return false;  // invalid relative index
858
0
    }
859
0
    return true;
860
0
  }
861
862
0
  return false;  // never reach here.
863
0
}
864
865
65.7k
static inline std::string parseString(const char **token) {
866
65.7k
  std::string s;
867
65.7k
  (*token) += strspn((*token), " \t");
868
65.7k
  size_t e = strcspn((*token), " \t\r");
869
65.7k
  s = std::string((*token), &(*token)[e]);
870
65.7k
  (*token) += e;
871
65.7k
  return s;
872
65.7k
}
873
874
16.4k
static inline int parseInt(const char **token) {
875
16.4k
  (*token) += strspn((*token), " \t");
876
16.4k
  int i = atoi((*token));
877
16.4k
  (*token) += strcspn((*token), " \t\r");
878
16.4k
  return i;
879
16.4k
}
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
26.3k
static bool tryParseDouble(const char *s, const char *s_end, double *result) {
909
26.3k
  if (s >= s_end) {
910
25.9k
    return false;
911
25.9k
  }
912
913
452
  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
452
  int exponent = 0;
922
923
  // NOTE: THESE MUST BE DECLARED HERE SINCE WE ARE NOT ALLOWED
924
  // TO JUMP OVER DEFINITIONS.
925
452
  char sign = '+';
926
452
  char exp_sign = '+';
927
452
  char const *curr = s;
928
929
  // How many characters were read in a loop.
930
452
  int read = 0;
931
  // Tells whether a loop terminated due to reaching s_end.
932
452
  bool end_not_reached = false;
933
452
  bool leading_decimal_dots = false;
934
935
  /*
936
          BEGIN PARSING.
937
  */
938
939
  // Find out what sign we've got.
940
452
  if (*curr == '+' || *curr == '-') {
941
38
    sign = *curr;
942
38
    curr++;
943
38
    if ((curr != s_end) && (*curr == '.')) {
944
      // accept. Somethig like `.7e+2`, `-.5234`
945
0
      leading_decimal_dots = true;
946
0
    }
947
414
  } else if (IS_DIGIT(*curr)) { /* Pass through. */
948
301
  } else if (*curr == '.') {
949
    // accept. Somethig like `.7e+2`, `-.5234`
950
27
    leading_decimal_dots = true;
951
274
  } else {
952
274
    goto fail;
953
274
  }
954
955
  // Read the integer part.
956
178
  end_not_reached = (curr != s_end);
957
178
  if (!leading_decimal_dots) {
958
640
    while (end_not_reached && IS_DIGIT(*curr)) {
959
489
      mantissa *= 10;
960
489
      mantissa += static_cast<int>(*curr - 0x30);
961
489
      curr++;
962
489
      read++;
963
489
      end_not_reached = (curr != s_end);
964
489
    }
965
966
    // We must make sure we actually got something.
967
151
    if (read == 0) goto fail;
968
151
  }
969
970
  // We allow numbers of form "#", "###" etc.
971
144
  if (!end_not_reached) goto assemble;
972
973
  // Read the decimal part.
974
127
  if (*curr == '.') {
975
37
    curr++;
976
37
    read = 1;
977
37
    end_not_reached = (curr != s_end);
978
145
    while (end_not_reached && IS_DIGIT(*curr)) {
979
108
      static const double pow_lut[] = {
980
108
          1.0, 0.1, 0.01, 0.001, 0.0001, 0.00001, 0.000001, 0.0000001,
981
108
      };
982
108
      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
108
      mantissa += static_cast<int>(*curr - 0x30) *
986
108
                  (read < lut_entries ? pow_lut[read] : std::pow(10.0, -read));
987
108
      read++;
988
108
      curr++;
989
108
      end_not_reached = (curr != s_end);
990
108
    }
991
90
  } else if (*curr == 'e' || *curr == 'E') {
992
48
  } else {
993
48
    goto assemble;
994
48
  }
995
996
79
  if (!end_not_reached) goto assemble;
997
998
  // Read the exponent part.
999
71
  if (*curr == 'e' || *curr == 'E') {
1000
52
    curr++;
1001
    // Figure out if a sign is present and if it is.
1002
52
    end_not_reached = (curr != s_end);
1003
52
    if (end_not_reached && (*curr == '+' || *curr == '-')) {
1004
42
      exp_sign = *curr;
1005
42
      curr++;
1006
42
    } else if (IS_DIGIT(*curr)) { /* Pass through. */
1007
10
    } else {
1008
      // Empty E is not allowed.
1009
0
      goto fail;
1010
0
    }
1011
1012
52
    read = 0;
1013
52
    end_not_reached = (curr != s_end);
1014
630
    while (end_not_reached && IS_DIGIT(*curr)) {
1015
      // To avoid annoying MSVC's min/max macro definiton,
1016
      // Use hardcoded int max value
1017
578
      if (exponent >
1018
578
          (2147483647 / 10)) {  // 2147483647 = std::numeric_limits<int>::max()
1019
        // Integer overflow
1020
0
        goto fail;
1021
0
      }
1022
578
      exponent *= 10;
1023
578
      exponent += static_cast<int>(*curr - 0x30);
1024
578
      curr++;
1025
578
      read++;
1026
578
      end_not_reached = (curr != s_end);
1027
578
    }
1028
52
    exponent *= (exp_sign == '+' ? 1 : -1);
1029
52
    if (read == 0) goto fail;
1030
52
  }
1031
1032
144
assemble:
1033
144
  *result = (sign == '+' ? 1 : -1) *
1034
144
            (exponent ? std::ldexp(mantissa * std::pow(5.0, exponent), exponent)
1035
144
                      : mantissa);
1036
144
  return true;
1037
308
fail:
1038
308
  return false;
1039
71
}
1040
1041
26.3k
static inline real_t parseReal(const char **token, double default_value = 0.0) {
1042
26.3k
  (*token) += strspn((*token), " \t");
1043
26.3k
  const char *end = (*token) + strcspn((*token), " \t\r");
1044
26.3k
  double val = default_value;
1045
26.3k
  tryParseDouble((*token), end, &val);
1046
26.3k
  real_t f = static_cast<real_t>(val);
1047
26.3k
  (*token) = end;
1048
26.3k
  return f;
1049
26.3k
}
1050
1051
7
static inline bool parseReal(const char **token, real_t *out) {
1052
7
  (*token) += strspn((*token), " \t");
1053
7
  const char *end = (*token) + strcspn((*token), " \t\r");
1054
7
  double val;
1055
7
  bool ret = tryParseDouble((*token), end, &val);
1056
7
  if (ret) {
1057
1
    real_t f = static_cast<real_t>(val);
1058
1
    (*out) = f;
1059
1
  }
1060
7
  (*token) = end;
1061
7
  return ret;
1062
7
}
1063
1064
static inline void parseReal2(real_t *x, real_t *y, const char **token,
1065
                              const double default_x = 0.0,
1066
1
                              const double default_y = 0.0) {
1067
1
  (*x) = parseReal(token, default_x);
1068
1
  (*y) = parseReal(token, default_y);
1069
1
}
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
202
                              const double default_z = 0.0) {
1075
202
  (*x) = parseReal(token, default_x);
1076
202
  (*y) = parseReal(token, default_y);
1077
202
  (*z) = parseReal(token, default_z);
1078
202
}
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
6
                                       const double default_z = 0.0) {
1102
  // TODO: Check error
1103
6
  (*x) = parseReal(token, default_x);
1104
6
  (*y) = parseReal(token, default_y);
1105
6
  (*z) = parseReal(token, default_z);
1106
1107
  // - 4 components(x, y, z, w) ot 6 components
1108
6
  bool has_r = parseReal(token, r);
1109
1110
6
  if (!has_r) {
1111
5
    (*r) = (*g) = (*b) = 1.0;
1112
5
    return 3;
1113
5
  }
1114
1115
1
  bool has_g = parseReal(token, g);
1116
1117
1
  if (!has_g) {
1118
1
    (*g) = (*b) = 1.0;
1119
1
    return 4;
1120
1
  }
1121
1122
0
  bool has_b = parseReal(token, b);
1123
1124
0
  if (!has_b) {
1125
0
    (*r) = (*g) = (*b) = 1.0;
1126
0
    return 3;  // treated as xyz
1127
0
  }
1128
1129
0
  return 6;
1130
0
}
1131
1132
324
static inline bool parseOnOff(const char **token, bool default_value = true) {
1133
324
  (*token) += strspn((*token), " \t");
1134
324
  const char *end = (*token) + strcspn((*token), " \t\r");
1135
1136
324
  bool ret = default_value;
1137
324
  if ((0 == strncmp((*token), "on", 2))) {
1138
215
    ret = true;
1139
215
  } else if ((0 == strncmp((*token), "off", 3))) {
1140
44
    ret = false;
1141
44
  }
1142
1143
324
  (*token) = end;
1144
324
  return ret;
1145
324
}
1146
1147
static inline texture_type_t parseTextureType(
1148
15
    const char **token, texture_type_t default_value = TEXTURE_TYPE_NONE) {
1149
15
  (*token) += strspn((*token), " \t");
1150
15
  const char *end = (*token) + strcspn((*token), " \t\r");
1151
15
  texture_type_t ty = default_value;
1152
1153
15
  if ((0 == strncmp((*token), "cube_top", strlen("cube_top")))) {
1154
0
    ty = TEXTURE_TYPE_CUBE_TOP;
1155
15
  } else if ((0 == strncmp((*token), "cube_bottom", strlen("cube_bottom")))) {
1156
2
    ty = TEXTURE_TYPE_CUBE_BOTTOM;
1157
13
  } else if ((0 == strncmp((*token), "cube_left", strlen("cube_left")))) {
1158
0
    ty = TEXTURE_TYPE_CUBE_LEFT;
1159
13
  } else if ((0 == strncmp((*token), "cube_right", strlen("cube_right")))) {
1160
0
    ty = TEXTURE_TYPE_CUBE_RIGHT;
1161
13
  } else if ((0 == strncmp((*token), "cube_front", strlen("cube_front")))) {
1162
7
    ty = TEXTURE_TYPE_CUBE_FRONT;
1163
7
  } else if ((0 == strncmp((*token), "cube_back", strlen("cube_back")))) {
1164
0
    ty = TEXTURE_TYPE_CUBE_BACK;
1165
6
  } else if ((0 == strncmp((*token), "sphere", strlen("sphere")))) {
1166
0
    ty = TEXTURE_TYPE_SPHERE;
1167
0
  }
1168
1169
15
  (*token) = end;
1170
15
  return ty;
1171
15
}
1172
1173
29
static tag_sizes parseTagTriple(const char **token) {
1174
29
  tag_sizes ts;
1175
1176
29
  (*token) += strspn((*token), " \t");
1177
29
  ts.num_ints = atoi((*token));
1178
29
  (*token) += strcspn((*token), "/ \t\r");
1179
29
  if ((*token)[0] != '/') {
1180
11
    return ts;
1181
11
  }
1182
1183
18
  (*token)++;  // Skip '/'
1184
1185
18
  (*token) += strspn((*token), " \t");
1186
18
  ts.num_reals = atoi((*token));
1187
18
  (*token) += strcspn((*token), "/ \t\r");
1188
18
  if ((*token)[0] != '/') {
1189
18
    return ts;
1190
18
  }
1191
0
  (*token)++;  // Skip '/'
1192
1193
0
  ts.num_strings = parseInt(token);
1194
1195
0
  return ts;
1196
18
}
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
72
                        vertex_index_t *ret, const warning_context &context) {
1201
72
  if (!ret) {
1202
0
    return false;
1203
0
  }
1204
1205
72
  vertex_index_t vi(-1);
1206
1207
72
  if (!fixIndex(atoi((*token)), vsize, &vi.v_idx, false, context)) {
1208
0
    return false;
1209
0
  }
1210
1211
72
  (*token) += strcspn((*token), "/ \t\r");
1212
72
  if ((*token)[0] != '/') {
1213
71
    (*ret) = vi;
1214
71
    return true;
1215
71
  }
1216
1
  (*token)++;
1217
1218
  // i//k
1219
1
  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
1
  if (!fixIndex(atoi((*token)), vtsize, &vi.vt_idx, true, context)) {
1231
0
    return false;
1232
0
  }
1233
1234
1
  (*token) += strcspn((*token), "/ \t\r");
1235
1
  if ((*token)[0] != '/') {
1236
1
    (*ret) = vi;
1237
1
    return true;
1238
1
  }
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.27k
                               const char *linebuf) {
1287
  // @todo { write more robust lexer and parser. }
1288
1.27k
  bool found_texname = false;
1289
1.27k
  std::string texture_name;
1290
1291
1.27k
  const char *token = linebuf;  // Assume line ends with NULL
1292
1293
2.90k
  while (!IS_NEW_LINE((*token))) {
1294
1.63k
    token += strspn(token, " \t");  // skip space
1295
1.63k
    if ((0 == strncmp(token, "-blendu", 7)) && IS_SPACE((token[7]))) {
1296
94
      token += 8;
1297
94
      texopt->blendu = parseOnOff(&token, /* default */ true);
1298
1.54k
    } else if ((0 == strncmp(token, "-blendv", 7)) && IS_SPACE((token[7]))) {
1299
16
      token += 8;
1300
16
      texopt->blendv = parseOnOff(&token, /* default */ true);
1301
1.52k
    } else if ((0 == strncmp(token, "-clamp", 6)) && IS_SPACE((token[6]))) {
1302
214
      token += 7;
1303
214
      texopt->clamp = parseOnOff(&token, /* default */ true);
1304
1.31k
    } else if ((0 == strncmp(token, "-boost", 6)) && IS_SPACE((token[6]))) {
1305
31
      token += 7;
1306
31
      texopt->sharpness = parseReal(&token, 1.0);
1307
1.28k
    } else if ((0 == strncmp(token, "-bm", 3)) && IS_SPACE((token[3]))) {
1308
5
      token += 4;
1309
5
      texopt->bump_multiplier = parseReal(&token, 1.0);
1310
1.27k
    } else if ((0 == strncmp(token, "-o", 2)) && IS_SPACE((token[2]))) {
1311
0
      token += 3;
1312
0
      parseReal3(&(texopt->origin_offset[0]), &(texopt->origin_offset[1]),
1313
0
                 &(texopt->origin_offset[2]), &token);
1314
1.27k
    } else if ((0 == strncmp(token, "-s", 2)) && IS_SPACE((token[2]))) {
1315
24
      token += 3;
1316
24
      parseReal3(&(texopt->scale[0]), &(texopt->scale[1]), &(texopt->scale[2]),
1317
24
                 &token, 1.0, 1.0, 1.0);
1318
1.25k
    } else if ((0 == strncmp(token, "-t", 2)) && IS_SPACE((token[2]))) {
1319
2
      token += 3;
1320
2
      parseReal3(&(texopt->turbulence[0]), &(texopt->turbulence[1]),
1321
2
                 &(texopt->turbulence[2]), &token);
1322
1.25k
    } else if ((0 == strncmp(token, "-type", 5)) && IS_SPACE((token[5]))) {
1323
15
      token += 5;
1324
15
      texopt->type = parseTextureType((&token), TEXTURE_TYPE_NONE);
1325
1.23k
    } else if ((0 == strncmp(token, "-texres", 7)) && IS_SPACE((token[7]))) {
1326
27
      token += 7;
1327
      // TODO(syoyo): Check if arg is int type.
1328
27
      texopt->texture_resolution = parseInt(&token);
1329
1.21k
    } else if ((0 == strncmp(token, "-imfchan", 8)) && IS_SPACE((token[8]))) {
1330
0
      token += 9;
1331
0
      token += strspn(token, " \t");
1332
0
      const char *end = token + strcspn(token, " \t\r");
1333
0
      if ((end - token) == 1) {  // Assume one char for -imfchan
1334
0
        texopt->imfchan = (*token);
1335
0
      }
1336
0
      token = end;
1337
1.21k
    } 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.21k
    } else if ((0 == strncmp(token, "-colorspace", 11)) &&
1341
53
               IS_SPACE((token[11]))) {
1342
11
      token += 12;
1343
11
      texopt->colorspace = parseString(&token);
1344
1.19k
    } 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.19k
      texture_name = std::string(token);
1356
1.19k
      token += texture_name.length();
1357
1.19k
#endif
1358
1359
1.19k
      found_texname = true;
1360
1.19k
    }
1361
1.63k
  }
1362
1363
1.27k
  if (found_texname) {
1364
1.19k
    (*texname) = texture_name;
1365
1.19k
    return true;
1366
1.19k
  } else {
1367
72
    return false;
1368
72
  }
1369
1.27k
}
1370
1371
854k
static void InitTexOpt(texture_option_t *texopt, const bool is_bump) {
1372
854k
  if (is_bump) {
1373
65.7k
    texopt->imfchan = 'l';
1374
788k
  } else {
1375
788k
    texopt->imfchan = 'm';
1376
788k
  }
1377
854k
  texopt->bump_multiplier = static_cast<real_t>(1.0);
1378
854k
  texopt->clamp = false;
1379
854k
  texopt->blendu = true;
1380
854k
  texopt->blendv = true;
1381
854k
  texopt->sharpness = static_cast<real_t>(1.0);
1382
854k
  texopt->brightness = static_cast<real_t>(0.0);
1383
854k
  texopt->contrast = static_cast<real_t>(1.0);
1384
854k
  texopt->origin_offset[0] = static_cast<real_t>(0.0);
1385
854k
  texopt->origin_offset[1] = static_cast<real_t>(0.0);
1386
854k
  texopt->origin_offset[2] = static_cast<real_t>(0.0);
1387
854k
  texopt->scale[0] = static_cast<real_t>(1.0);
1388
854k
  texopt->scale[1] = static_cast<real_t>(1.0);
1389
854k
  texopt->scale[2] = static_cast<real_t>(1.0);
1390
854k
  texopt->turbulence[0] = static_cast<real_t>(0.0);
1391
854k
  texopt->turbulence[1] = static_cast<real_t>(0.0);
1392
854k
  texopt->turbulence[2] = static_cast<real_t>(0.0);
1393
854k
  texopt->texture_resolution = -1;
1394
854k
  texopt->type = TEXTURE_TYPE_NONE;
1395
854k
}
1396
1397
65.7k
static void InitMaterial(material_t *material) {
1398
65.7k
  InitTexOpt(&material->ambient_texopt, /* is_bump */ false);
1399
65.7k
  InitTexOpt(&material->diffuse_texopt, /* is_bump */ false);
1400
65.7k
  InitTexOpt(&material->specular_texopt, /* is_bump */ false);
1401
65.7k
  InitTexOpt(&material->specular_highlight_texopt, /* is_bump */ false);
1402
65.7k
  InitTexOpt(&material->bump_texopt, /* is_bump */ true);
1403
65.7k
  InitTexOpt(&material->displacement_texopt, /* is_bump */ false);
1404
65.7k
  InitTexOpt(&material->alpha_texopt, /* is_bump */ false);
1405
65.7k
  InitTexOpt(&material->reflection_texopt, /* is_bump */ false);
1406
65.7k
  InitTexOpt(&material->roughness_texopt, /* is_bump */ false);
1407
65.7k
  InitTexOpt(&material->metallic_texopt, /* is_bump */ false);
1408
65.7k
  InitTexOpt(&material->sheen_texopt, /* is_bump */ false);
1409
65.7k
  InitTexOpt(&material->emissive_texopt, /* is_bump */ false);
1410
65.7k
  InitTexOpt(&material->normal_texopt,
1411
65.7k
             /* is_bump */ false);  // @fixme { is_bump will be true? }
1412
65.7k
  material->name = "";
1413
65.7k
  material->ambient_texname = "";
1414
65.7k
  material->diffuse_texname = "";
1415
65.7k
  material->specular_texname = "";
1416
65.7k
  material->specular_highlight_texname = "";
1417
65.7k
  material->bump_texname = "";
1418
65.7k
  material->displacement_texname = "";
1419
65.7k
  material->reflection_texname = "";
1420
65.7k
  material->alpha_texname = "";
1421
262k
  for (int i = 0; i < 3; i++) {
1422
197k
    material->ambient[i] = static_cast<real_t>(0.0);
1423
197k
    material->diffuse[i] = static_cast<real_t>(0.0);
1424
197k
    material->specular[i] = static_cast<real_t>(0.0);
1425
197k
    material->transmittance[i] = static_cast<real_t>(0.0);
1426
197k
    material->emission[i] = static_cast<real_t>(0.0);
1427
197k
  }
1428
65.7k
  material->illum = 0;
1429
65.7k
  material->dissolve = static_cast<real_t>(1.0);
1430
65.7k
  material->shininess = static_cast<real_t>(1.0);
1431
65.7k
  material->ior = static_cast<real_t>(1.0);
1432
1433
65.7k
  material->roughness = static_cast<real_t>(0.0);
1434
65.7k
  material->metallic = static_cast<real_t>(0.0);
1435
65.7k
  material->sheen = static_cast<real_t>(0.0);
1436
65.7k
  material->clearcoat_thickness = static_cast<real_t>(0.0);
1437
65.7k
  material->clearcoat_roughness = static_cast<real_t>(0.0);
1438
65.7k
  material->anisotropy_rotation = static_cast<real_t>(0.0);
1439
65.7k
  material->anisotropy = static_cast<real_t>(0.0);
1440
65.7k
  material->roughness_texname = "";
1441
65.7k
  material->metallic_texname = "";
1442
65.7k
  material->sheen_texname = "";
1443
65.7k
  material->emissive_texname = "";
1444
65.7k
  material->normal_texname = "";
1445
1446
65.7k
  material->unknown_parameter.clear();
1447
65.7k
}
1448
1449
// code from https://wrf.ecse.rpi.edu//Research/Short_Notes/pnpoly.html
1450
template <typename T>
1451
6
static int pnpoly(int nvert, T *vertx, T *verty, T testx, T testy) {
1452
6
  int i, j, c = 0;
1453
24
  for (i = 0, j = nvert - 1; i < nvert; j = i++) {
1454
18
    if (((verty[i] > testy) != (verty[j] > testy)) &&
1455
6
        (testx <
1456
6
         (vertx[j] - vertx[i]) * (testy - verty[i]) / (verty[j] - verty[i]) +
1457
6
             vertx[i]))
1458
0
      c = !c;
1459
18
  }
1460
6
  return c;
1461
6
}
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
63
                                std::string *warn) {
1498
63
  if (prim_group.IsEmpty()) {
1499
42
    return false;
1500
42
  }
1501
1502
21
  shape->name = name;
1503
1504
  // polygon
1505
21
  if (!prim_group.faceGroup.empty()) {
1506
    // Flatten vertices and indices
1507
24
    for (size_t i = 0; i < prim_group.faceGroup.size(); i++) {
1508
18
      const face_t &face = prim_group.faceGroup[i];
1509
1510
18
      size_t npolys = face.vertex_indices.size();
1511
1512
18
      if (npolys < 3) {
1513
        // Face must have 3+ vertices.
1514
4
        if (warn) {
1515
4
          (*warn) += "Degenerated face found\n.";
1516
4
        }
1517
4
        continue;
1518
4
      }
1519
1520
14
      if (triangulate && npolys != 3) {
1521
10
        if (npolys == 4) {
1522
9
          vertex_index_t i0 = face.vertex_indices[0];
1523
9
          vertex_index_t i1 = face.vertex_indices[1];
1524
9
          vertex_index_t i2 = face.vertex_indices[2];
1525
9
          vertex_index_t i3 = face.vertex_indices[3];
1526
1527
9
          size_t vi0 = size_t(i0.v_idx);
1528
9
          size_t vi1 = size_t(i1.v_idx);
1529
9
          size_t vi2 = size_t(i2.v_idx);
1530
9
          size_t vi3 = size_t(i3.v_idx);
1531
1532
9
          if (((3 * vi0 + 2) >= v.size()) || ((3 * vi1 + 2) >= v.size()) ||
1533
9
              ((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
8
            if (warn) {
1537
8
              (*warn) += "Face with invalid vertex index found.\n";
1538
8
            }
1539
8
            continue;
1540
8
          }
1541
1542
1
          real_t v0x = v[vi0 * 3 + 0];
1543
1
          real_t v0y = v[vi0 * 3 + 1];
1544
1
          real_t v0z = v[vi0 * 3 + 2];
1545
1
          real_t v1x = v[vi1 * 3 + 0];
1546
1
          real_t v1y = v[vi1 * 3 + 1];
1547
1
          real_t v1z = v[vi1 * 3 + 2];
1548
1
          real_t v2x = v[vi2 * 3 + 0];
1549
1
          real_t v2y = v[vi2 * 3 + 1];
1550
1
          real_t v2z = v[vi2 * 3 + 2];
1551
1
          real_t v3x = v[vi3 * 3 + 0];
1552
1
          real_t v3y = v[vi3 * 3 + 1];
1553
1
          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
1
          real_t e02x = v2x - v0x;
1574
1
          real_t e02y = v2y - v0y;
1575
1
          real_t e02z = v2z - v0z;
1576
1
          real_t e13x = v3x - v1x;
1577
1
          real_t e13y = v3y - v1y;
1578
1
          real_t e13z = v3z - v1z;
1579
1580
1
          real_t sqr02 = e02x * e02x + e02y * e02y + e02z * e02z;
1581
1
          real_t sqr13 = e13x * e13x + e13y * e13y + e13z * e13z;
1582
1583
1
          index_t idx0, idx1, idx2, idx3;
1584
1585
1
          idx0.vertex_index = i0.v_idx;
1586
1
          idx0.normal_index = i0.vn_idx;
1587
1
          idx0.texcoord_index = i0.vt_idx;
1588
1
          idx1.vertex_index = i1.v_idx;
1589
1
          idx1.normal_index = i1.vn_idx;
1590
1
          idx1.texcoord_index = i1.vt_idx;
1591
1
          idx2.vertex_index = i2.v_idx;
1592
1
          idx2.normal_index = i2.vn_idx;
1593
1
          idx2.texcoord_index = i2.vt_idx;
1594
1
          idx3.vertex_index = i3.v_idx;
1595
1
          idx3.normal_index = i3.vn_idx;
1596
1
          idx3.texcoord_index = i3.vt_idx;
1597
1598
1
          if (sqr02 < sqr13) {
1599
            // [0, 1, 2], [0, 2, 3]
1600
1
            shape->mesh.indices.push_back(idx0);
1601
1
            shape->mesh.indices.push_back(idx1);
1602
1
            shape->mesh.indices.push_back(idx2);
1603
1604
1
            shape->mesh.indices.push_back(idx0);
1605
1
            shape->mesh.indices.push_back(idx2);
1606
1
            shape->mesh.indices.push_back(idx3);
1607
1
          } else {
1608
            // [0, 1, 3], [1, 2, 3]
1609
0
            shape->mesh.indices.push_back(idx0);
1610
0
            shape->mesh.indices.push_back(idx1);
1611
0
            shape->mesh.indices.push_back(idx3);
1612
1613
0
            shape->mesh.indices.push_back(idx1);
1614
0
            shape->mesh.indices.push_back(idx2);
1615
0
            shape->mesh.indices.push_back(idx3);
1616
0
          }
1617
1618
          // Two triangle faces
1619
1
          shape->mesh.num_face_vertices.push_back(3);
1620
1
          shape->mesh.num_face_vertices.push_back(3);
1621
1622
1
          shape->mesh.material_ids.push_back(material_id);
1623
1
          shape->mesh.material_ids.push_back(material_id);
1624
1625
1
          shape->mesh.smoothing_group_ids.push_back(face.smoothing_group_id);
1626
1
          shape->mesh.smoothing_group_ids.push_back(face.smoothing_group_id);
1627
1628
1
        } 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
          vertex_index_t i0 = face.vertex_indices[0];
1753
1
          vertex_index_t i1(-1);
1754
1
          vertex_index_t i2 = face.vertex_indices[1];
1755
1756
          // find the two axes to work in
1757
1
          size_t axes[2] = {1, 2};
1758
3
          for (size_t k = 0; k < npolys; ++k) {
1759
3
            i0 = face.vertex_indices[(k + 0) % npolys];
1760
3
            i1 = face.vertex_indices[(k + 1) % npolys];
1761
3
            i2 = face.vertex_indices[(k + 2) % npolys];
1762
3
            size_t vi0 = size_t(i0.v_idx);
1763
3
            size_t vi1 = size_t(i1.v_idx);
1764
3
            size_t vi2 = size_t(i2.v_idx);
1765
1766
3
            if (((3 * vi0 + 2) >= v.size()) || ((3 * vi1 + 2) >= v.size()) ||
1767
3
                ((3 * vi2 + 2) >= v.size())) {
1768
              // Invalid triangle.
1769
              // FIXME(syoyo): Is it ok to simply skip this invalid triangle?
1770
0
              continue;
1771
0
            }
1772
3
            real_t v0x = v[vi0 * 3 + 0];
1773
3
            real_t v0y = v[vi0 * 3 + 1];
1774
3
            real_t v0z = v[vi0 * 3 + 2];
1775
3
            real_t v1x = v[vi1 * 3 + 0];
1776
3
            real_t v1y = v[vi1 * 3 + 1];
1777
3
            real_t v1z = v[vi1 * 3 + 2];
1778
3
            real_t v2x = v[vi2 * 3 + 0];
1779
3
            real_t v2y = v[vi2 * 3 + 1];
1780
3
            real_t v2z = v[vi2 * 3 + 2];
1781
3
            real_t e0x = v1x - v0x;
1782
3
            real_t e0y = v1y - v0y;
1783
3
            real_t e0z = v1z - v0z;
1784
3
            real_t e1x = v2x - v1x;
1785
3
            real_t e1y = v2y - v1y;
1786
3
            real_t e1z = v2z - v1z;
1787
3
            real_t cx = std::fabs(e0y * e1z - e0z * e1y);
1788
3
            real_t cy = std::fabs(e0z * e1x - e0x * e1z);
1789
3
            real_t cz = std::fabs(e0x * e1y - e0y * e1x);
1790
3
            const real_t epsilon = std::numeric_limits<real_t>::epsilon();
1791
            // std::cout << "cx " << cx << ", cy " << cy << ", cz " << cz <<
1792
            // "\n";
1793
3
            if (cx > epsilon || cy > epsilon || cz > epsilon) {
1794
              // std::cout << "corner\n";
1795
              // found a corner
1796
1
              if (cx > cy && cx > cz) {
1797
                // std::cout << "pattern0\n";
1798
1
              } else {
1799
                // std::cout << "axes[0] = 0\n";
1800
1
                axes[0] = 0;
1801
1
                if (cz > cx && cz > cy) {
1802
                  // std::cout << "axes[1] = 1\n";
1803
0
                  axes[1] = 1;
1804
0
                }
1805
1
              }
1806
1
              break;
1807
1
            }
1808
3
          }
1809
1810
1
          face_t remainingFace = face;  // copy
1811
1
          size_t guess_vert = 0;
1812
1
          vertex_index_t ind[3];
1813
1
          real_t vx[3];
1814
1
          real_t vy[3];
1815
1816
          // How many iterations can we do without decreasing the remaining
1817
          // vertices.
1818
1
          size_t remainingIterations = face.vertex_indices.size();
1819
1
          size_t previousRemainingVertices =
1820
1
              remainingFace.vertex_indices.size();
1821
1822
4
          while (remainingFace.vertex_indices.size() > 3 &&
1823
3
                 remainingIterations > 0) {
1824
            // std::cout << "remainingIterations " << remainingIterations <<
1825
            // "\n";
1826
1827
3
            npolys = remainingFace.vertex_indices.size();
1828
3
            if (guess_vert >= npolys) {
1829
0
              guess_vert -= npolys;
1830
0
            }
1831
1832
3
            if (previousRemainingVertices != npolys) {
1833
              // The number of remaining vertices decreased. Reset counters.
1834
2
              previousRemainingVertices = npolys;
1835
2
              remainingIterations = npolys;
1836
2
            } else {
1837
              // We didn't consume a vertex on previous iteration, reduce the
1838
              // available iterations.
1839
1
              remainingIterations--;
1840
1
            }
1841
1842
12
            for (size_t k = 0; k < 3; k++) {
1843
9
              ind[k] = remainingFace.vertex_indices[(guess_vert + k) % npolys];
1844
9
              size_t vi = size_t(ind[k].v_idx);
1845
9
              if (((vi * 3 + axes[0]) >= v.size()) ||
1846
9
                  ((vi * 3 + axes[1]) >= v.size())) {
1847
                // ???
1848
0
                vx[k] = static_cast<real_t>(0.0);
1849
0
                vy[k] = static_cast<real_t>(0.0);
1850
9
              } else {
1851
9
                vx[k] = v[vi * 3 + axes[0]];
1852
9
                vy[k] = v[vi * 3 + axes[1]];
1853
9
              }
1854
9
            }
1855
1856
            //
1857
            // area is calculated per face
1858
            //
1859
3
            real_t e0x = vx[1] - vx[0];
1860
3
            real_t e0y = vy[1] - vy[0];
1861
3
            real_t e1x = vx[2] - vx[1];
1862
3
            real_t e1y = vy[2] - vy[1];
1863
3
            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
3
            real_t area =
1869
3
                (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
3
            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
3
            bool overlap = false;
1881
9
            for (size_t otherVert = 3; otherVert < npolys; ++otherVert) {
1882
6
              size_t idx = (guess_vert + otherVert) % npolys;
1883
1884
6
              if (idx >= remainingFace.vertex_indices.size()) {
1885
                // std::cout << "???0\n";
1886
                // ???
1887
0
                continue;
1888
0
              }
1889
1890
6
              size_t ovi = size_t(remainingFace.vertex_indices[idx].v_idx);
1891
1892
6
              if (((ovi * 3 + axes[0]) >= v.size()) ||
1893
6
                  ((ovi * 3 + axes[1]) >= v.size())) {
1894
                // std::cout << "???1\n";
1895
                // ???
1896
0
                continue;
1897
0
              }
1898
6
              real_t tx = v[ovi * 3 + axes[0]];
1899
6
              real_t ty = v[ovi * 3 + axes[1]];
1900
6
              if (pnpoly(3, vx, vy, tx, ty)) {
1901
                // std::cout << "overlap\n";
1902
0
                overlap = true;
1903
0
                break;
1904
0
              }
1905
6
            }
1906
1907
3
            if (overlap) {
1908
              // std::cout << "overlap2\n";
1909
0
              guess_vert += 1;
1910
0
              continue;
1911
0
            }
1912
1913
            // this triangle is an ear
1914
3
            {
1915
3
              index_t idx0, idx1, idx2;
1916
3
              idx0.vertex_index = ind[0].v_idx;
1917
3
              idx0.normal_index = ind[0].vn_idx;
1918
3
              idx0.texcoord_index = ind[0].vt_idx;
1919
3
              idx1.vertex_index = ind[1].v_idx;
1920
3
              idx1.normal_index = ind[1].vn_idx;
1921
3
              idx1.texcoord_index = ind[1].vt_idx;
1922
3
              idx2.vertex_index = ind[2].v_idx;
1923
3
              idx2.normal_index = ind[2].vn_idx;
1924
3
              idx2.texcoord_index = ind[2].vt_idx;
1925
1926
3
              shape->mesh.indices.push_back(idx0);
1927
3
              shape->mesh.indices.push_back(idx1);
1928
3
              shape->mesh.indices.push_back(idx2);
1929
1930
3
              shape->mesh.num_face_vertices.push_back(3);
1931
3
              shape->mesh.material_ids.push_back(material_id);
1932
3
              shape->mesh.smoothing_group_ids.push_back(
1933
3
                  face.smoothing_group_id);
1934
3
            }
1935
1936
            // remove v1 from the list
1937
3
            size_t removed_vert_index = (guess_vert + 1) % npolys;
1938
12
            while (removed_vert_index + 1 < npolys) {
1939
9
              remainingFace.vertex_indices[removed_vert_index] =
1940
9
                  remainingFace.vertex_indices[removed_vert_index + 1];
1941
9
              removed_vert_index += 1;
1942
9
            }
1943
3
            remainingFace.vertex_indices.pop_back();
1944
3
          }
1945
1946
          // std::cout << "remainingFace.vi.size = " <<
1947
          // remainingFace.vertex_indices.size() << "\n";
1948
1
          if (remainingFace.vertex_indices.size() == 3) {
1949
1
            i0 = remainingFace.vertex_indices[0];
1950
1
            i1 = remainingFace.vertex_indices[1];
1951
1
            i2 = remainingFace.vertex_indices[2];
1952
1
            {
1953
1
              index_t idx0, idx1, idx2;
1954
1
              idx0.vertex_index = i0.v_idx;
1955
1
              idx0.normal_index = i0.vn_idx;
1956
1
              idx0.texcoord_index = i0.vt_idx;
1957
1
              idx1.vertex_index = i1.v_idx;
1958
1
              idx1.normal_index = i1.vn_idx;
1959
1
              idx1.texcoord_index = i1.vt_idx;
1960
1
              idx2.vertex_index = i2.v_idx;
1961
1
              idx2.normal_index = i2.vn_idx;
1962
1
              idx2.texcoord_index = i2.vt_idx;
1963
1964
1
              shape->mesh.indices.push_back(idx0);
1965
1
              shape->mesh.indices.push_back(idx1);
1966
1
              shape->mesh.indices.push_back(idx2);
1967
1968
1
              shape->mesh.num_face_vertices.push_back(3);
1969
1
              shape->mesh.material_ids.push_back(material_id);
1970
1
              shape->mesh.smoothing_group_ids.push_back(
1971
1
                  face.smoothing_group_id);
1972
1
            }
1973
1
          }
1974
1
#endif
1975
1
        }  // npolys
1976
10
      } else {
1977
16
        for (size_t k = 0; k < npolys; k++) {
1978
12
          index_t idx;
1979
12
          idx.vertex_index = face.vertex_indices[k].v_idx;
1980
12
          idx.normal_index = face.vertex_indices[k].vn_idx;
1981
12
          idx.texcoord_index = face.vertex_indices[k].vt_idx;
1982
12
          shape->mesh.indices.push_back(idx);
1983
12
        }
1984
1985
4
        shape->mesh.num_face_vertices.push_back(
1986
4
            static_cast<unsigned int>(npolys));
1987
4
        shape->mesh.material_ids.push_back(material_id);  // per face
1988
4
        shape->mesh.smoothing_group_ids.push_back(
1989
4
            face.smoothing_group_id);  // per face
1990
4
      }
1991
14
    }
1992
1993
6
    shape->mesh.tags = tags;
1994
6
  }
1995
1996
  // line
1997
21
  if (!prim_group.lineGroup.empty()) {
1998
    // Flatten indices
1999
6
    for (size_t i = 0; i < prim_group.lineGroup.size(); i++) {
2000
10
      for (size_t j = 0; j < prim_group.lineGroup[i].vertex_indices.size();
2001
6
           j++) {
2002
6
        const vertex_index_t &vi = prim_group.lineGroup[i].vertex_indices[j];
2003
2004
6
        index_t idx;
2005
6
        idx.vertex_index = vi.v_idx;
2006
6
        idx.normal_index = vi.vn_idx;
2007
6
        idx.texcoord_index = vi.vt_idx;
2008
2009
6
        shape->lines.indices.push_back(idx);
2010
6
      }
2011
2012
4
      shape->lines.num_line_vertices.push_back(
2013
4
          int(prim_group.lineGroup[i].vertex_indices.size()));
2014
4
    }
2015
2
  }
2016
2017
  // points
2018
21
  if (!prim_group.pointsGroup.empty()) {
2019
    // Flatten & convert indices
2020
41
    for (size_t i = 0; i < prim_group.pointsGroup.size(); i++) {
2021
46
      for (size_t j = 0; j < prim_group.pointsGroup[i].vertex_indices.size();
2022
23
           j++) {
2023
23
        const vertex_index_t &vi = prim_group.pointsGroup[i].vertex_indices[j];
2024
2025
23
        index_t idx;
2026
23
        idx.vertex_index = vi.v_idx;
2027
23
        idx.normal_index = vi.vn_idx;
2028
23
        idx.texcoord_index = vi.vt_idx;
2029
2030
23
        shape->points.indices.push_back(idx);
2031
23
      }
2032
23
    }
2033
18
  }
2034
2035
21
  return true;
2036
63
}
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
53
                        std::vector<std::string> &elems) {
2042
53
  std::string token;
2043
2044
53
  bool escaping = false;
2045
5.23M
  for (size_t i = 0; i < s.size(); ++i) {
2046
5.23M
    char ch = s[i];
2047
5.23M
    if (escaping) {
2048
9
      escaping = false;
2049
5.23M
    } else if (ch == escape) {
2050
9
      escaping = true;
2051
9
      continue;
2052
5.23M
    } else if (ch == delim) {
2053
35
      if (!token.empty()) {
2054
35
        elems.push_back(token);
2055
35
      }
2056
35
      token.clear();
2057
35
      continue;
2058
35
    }
2059
5.23M
    token += ch;
2060
5.23M
  }
2061
2062
53
  elems.push_back(token);
2063
53
}
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
36
             std::string *warning, std::string *err) {
2083
36
  (void)err;
2084
2085
  // Create a default material anyway.
2086
36
  material_t material;
2087
36
  InitMaterial(&material);
2088
2089
  // Issue 43. `d` wins against `Tr` since `Tr` is not in the MTL specification.
2090
36
  bool has_d = false;
2091
36
  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
36
  bool has_kd = false;
2096
2097
36
  std::stringstream warn_ss;
2098
2099
36
  size_t line_no = 0;
2100
36
  std::string linebuf;
2101
88.9k
  while (inStream->peek() != -1) {
2102
88.9k
    safeGetline(*inStream, linebuf);
2103
88.9k
    line_no++;
2104
2105
    // Trim trailing whitespace.
2106
88.9k
    if (linebuf.size() > 0) {
2107
88.4k
      linebuf = linebuf.substr(0, linebuf.find_last_not_of(" \t") + 1);
2108
88.4k
    }
2109
2110
    // Trim newline '\r\n' or '\n'
2111
88.9k
    if (linebuf.size() > 0) {
2112
87.4k
      if (linebuf[linebuf.size() - 1] == '\n')
2113
0
        linebuf.erase(linebuf.size() - 1);
2114
87.4k
    }
2115
88.9k
    if (linebuf.size() > 0) {
2116
87.4k
      if (linebuf[linebuf.size() - 1] == '\r')
2117
0
        linebuf.erase(linebuf.size() - 1);
2118
87.4k
    }
2119
2120
    // Skip if empty line.
2121
88.9k
    if (linebuf.empty()) {
2122
1.45k
      continue;
2123
1.45k
    }
2124
87.4k
    if (line_no == 1) {
2125
32
      linebuf = removeUtf8Bom(linebuf);
2126
32
    }
2127
2128
    // Skip leading space.
2129
87.4k
    const char *token = linebuf.c_str();
2130
87.4k
    token += strspn(token, " \t");
2131
2132
87.4k
    assert(token);
2133
87.4k
    if (token[0] == '\0') continue;  // empty line
2134
2135
87.3k
    if (token[0] == '#') continue;  // comment line
2136
2137
    // new mtl
2138
87.3k
    if ((0 == strncmp(token, "newmtl", 6)) && IS_SPACE((token[6]))) {
2139
      // flush previous material.
2140
65.6k
      if (!material.name.empty()) {
2141
65.6k
        material_map->insert(std::pair<std::string, int>(
2142
65.6k
            material.name, static_cast<int>(materials->size())));
2143
65.6k
        materials->push_back(material);
2144
65.6k
      }
2145
2146
      // initial temporary material
2147
65.6k
      InitMaterial(&material);
2148
2149
65.6k
      has_d = false;
2150
65.6k
      has_tr = false;
2151
65.6k
      has_kd = false;
2152
2153
      // set new mtl name
2154
65.6k
      token += 7;
2155
65.6k
      {
2156
65.6k
        std::string namebuf = parseString(&token);
2157
        // TODO: empty name check?
2158
65.6k
        if (namebuf.empty()) {
2159
0
          if (warning) {
2160
0
            (*warning) += "empty material name in `newmtl`\n";
2161
0
          }
2162
0
        }
2163
65.6k
        material.name = namebuf;
2164
65.6k
      }
2165
65.6k
      continue;
2166
65.6k
    }
2167
2168
    // ambient
2169
21.6k
    if (token[0] == 'K' && token[1] == 'a' && IS_SPACE((token[2]))) {
2170
48
      token += 2;
2171
48
      real_t r, g, b;
2172
48
      parseReal3(&r, &g, &b, &token);
2173
48
      material.ambient[0] = r;
2174
48
      material.ambient[1] = g;
2175
48
      material.ambient[2] = b;
2176
48
      continue;
2177
48
    }
2178
2179
    // diffuse
2180
21.6k
    if (token[0] == 'K' && token[1] == 'd' && IS_SPACE((token[2]))) {
2181
42
      token += 2;
2182
42
      real_t r, g, b;
2183
42
      parseReal3(&r, &g, &b, &token);
2184
42
      material.diffuse[0] = r;
2185
42
      material.diffuse[1] = g;
2186
42
      material.diffuse[2] = b;
2187
42
      has_kd = true;
2188
42
      continue;
2189
42
    }
2190
2191
    // specular
2192
21.5k
    if (token[0] == 'K' && token[1] == 's' && IS_SPACE((token[2]))) {
2193
13
      token += 2;
2194
13
      real_t r, g, b;
2195
13
      parseReal3(&r, &g, &b, &token);
2196
13
      material.specular[0] = r;
2197
13
      material.specular[1] = g;
2198
13
      material.specular[2] = b;
2199
13
      continue;
2200
13
    }
2201
2202
    // transmittance
2203
21.5k
    if ((token[0] == 'K' && token[1] == 't' && IS_SPACE((token[2]))) ||
2204
21.5k
        (token[0] == 'T' && token[1] == 'f' && IS_SPACE((token[2])))) {
2205
10
      token += 2;
2206
10
      real_t r, g, b;
2207
10
      parseReal3(&r, &g, &b, &token);
2208
10
      material.transmittance[0] = r;
2209
10
      material.transmittance[1] = g;
2210
10
      material.transmittance[2] = b;
2211
10
      continue;
2212
10
    }
2213
2214
    // ior(index of refraction)
2215
21.5k
    if (token[0] == 'N' && token[1] == 'i' && IS_SPACE((token[2]))) {
2216
60
      token += 2;
2217
60
      material.ior = parseReal(&token);
2218
60
      continue;
2219
60
    }
2220
2221
    // emission
2222
21.4k
    if (token[0] == 'K' && token[1] == 'e' && IS_SPACE(token[2])) {
2223
0
      token += 2;
2224
0
      real_t r, g, b;
2225
0
      parseReal3(&r, &g, &b, &token);
2226
0
      material.emission[0] = r;
2227
0
      material.emission[1] = g;
2228
0
      material.emission[2] = b;
2229
0
      continue;
2230
0
    }
2231
2232
    // shininess
2233
21.4k
    if (token[0] == 'N' && token[1] == 's' && IS_SPACE(token[2])) {
2234
0
      token += 2;
2235
0
      material.shininess = parseReal(&token);
2236
0
      continue;
2237
0
    }
2238
2239
    // illum model
2240
21.4k
    if (0 == strncmp(token, "illum", 5) && IS_SPACE(token[5])) {
2241
2
      token += 6;
2242
2
      material.illum = parseInt(&token);
2243
2
      continue;
2244
2
    }
2245
2246
    // dissolve
2247
21.4k
    if ((token[0] == 'd' && IS_SPACE(token[1]))) {
2248
2
      token += 1;
2249
2
      material.dissolve = parseReal(&token);
2250
2251
2
      if (has_tr) {
2252
2
        warn_ss << "Both `d` and `Tr` parameters defined for \""
2253
2
                << material.name
2254
2
                << "\". Use the value of `d` for dissolve (line " << line_no
2255
2
                << " in .mtl.)\n";
2256
2
      }
2257
2
      has_d = true;
2258
2
      continue;
2259
2
    }
2260
21.4k
    if (token[0] == 'T' && token[1] == 'r' && IS_SPACE(token[2])) {
2261
22
      token += 2;
2262
22
      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
22
      } 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
22
        material.dissolve = static_cast<real_t>(1.0) - parseReal(&token);
2273
22
      }
2274
22
      has_tr = true;
2275
22
      continue;
2276
22
    }
2277
2278
    // PBR: roughness
2279
21.4k
    if (token[0] == 'P' && token[1] == 'r' && IS_SPACE(token[2])) {
2280
6
      token += 2;
2281
6
      material.roughness = parseReal(&token);
2282
6
      continue;
2283
6
    }
2284
2285
    // PBR: metallic
2286
21.4k
    if (token[0] == 'P' && token[1] == 'm' && IS_SPACE(token[2])) {
2287
10
      token += 2;
2288
10
      material.metallic = parseReal(&token);
2289
10
      continue;
2290
10
    }
2291
2292
    // PBR: sheen
2293
21.4k
    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
21.4k
    if (token[0] == 'P' && token[1] == 'c' && IS_SPACE(token[2])) {
2301
0
      token += 2;
2302
0
      material.clearcoat_thickness = parseReal(&token);
2303
0
      continue;
2304
0
    }
2305
2306
    // PBR: clearcoat roughness
2307
21.4k
    if ((0 == strncmp(token, "Pcr", 3)) && IS_SPACE(token[3])) {
2308
3
      token += 4;
2309
3
      material.clearcoat_roughness = parseReal(&token);
2310
3
      continue;
2311
3
    }
2312
2313
    // PBR: anisotropy
2314
21.4k
    if ((0 == strncmp(token, "aniso", 5)) && IS_SPACE(token[5])) {
2315
0
      token += 6;
2316
0
      material.anisotropy = parseReal(&token);
2317
0
      continue;
2318
0
    }
2319
2320
    // PBR: anisotropy rotation
2321
21.4k
    if ((0 == strncmp(token, "anisor", 6)) && IS_SPACE(token[6])) {
2322
34
      token += 7;
2323
34
      material.anisotropy_rotation = parseReal(&token);
2324
34
      continue;
2325
34
    }
2326
2327
    // ambient or ambient occlusion texture
2328
21.4k
    if ((0 == strncmp(token, "map_Ka", 6)) && IS_SPACE(token[6])) {
2329
5
      token += 7;
2330
5
      ParseTextureNameAndOption(&(material.ambient_texname),
2331
5
                                &(material.ambient_texopt), token);
2332
5
      continue;
2333
5
    }
2334
2335
    // diffuse texture
2336
21.4k
    if ((0 == strncmp(token, "map_Kd", 6)) && IS_SPACE(token[6])) {
2337
18
      token += 7;
2338
18
      ParseTextureNameAndOption(&(material.diffuse_texname),
2339
18
                                &(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
18
      if (!has_kd) {
2344
18
        material.diffuse[0] = static_cast<real_t>(0.6);
2345
18
        material.diffuse[1] = static_cast<real_t>(0.6);
2346
18
        material.diffuse[2] = static_cast<real_t>(0.6);
2347
18
      }
2348
2349
18
      continue;
2350
18
    }
2351
2352
    // specular texture
2353
21.3k
    if ((0 == strncmp(token, "map_Ks", 6)) && IS_SPACE(token[6])) {
2354
658
      token += 7;
2355
658
      ParseTextureNameAndOption(&(material.specular_texname),
2356
658
                                &(material.specular_texopt), token);
2357
658
      continue;
2358
658
    }
2359
2360
    // specular highlight texture
2361
20.7k
    if ((0 == strncmp(token, "map_Ns", 6)) && IS_SPACE(token[6])) {
2362
5
      token += 7;
2363
5
      ParseTextureNameAndOption(&(material.specular_highlight_texname),
2364
5
                                &(material.specular_highlight_texopt), token);
2365
5
      continue;
2366
5
    }
2367
2368
    // bump texture
2369
20.7k
    if (((0 == strncmp(token, "map_bump", 8)) ||
2370
20.7k
         (0 == strncmp(token, "map_Bump", 8))) &&
2371
12
        IS_SPACE(token[8])) {
2372
12
      token += 9;
2373
12
      ParseTextureNameAndOption(&(material.bump_texname),
2374
12
                                &(material.bump_texopt), token);
2375
12
      continue;
2376
12
    }
2377
2378
    // bump texture
2379
20.7k
    if ((0 == strncmp(token, "bump", 4)) && IS_SPACE(token[4])) {
2380
58
      token += 5;
2381
58
      ParseTextureNameAndOption(&(material.bump_texname),
2382
58
                                &(material.bump_texopt), token);
2383
58
      continue;
2384
58
    }
2385
2386
    // alpha texture
2387
20.6k
    if ((0 == strncmp(token, "map_d", 5)) && IS_SPACE(token[5])) {
2388
2
      token += 6;
2389
2
      material.alpha_texname = token;
2390
2
      ParseTextureNameAndOption(&(material.alpha_texname),
2391
2
                                &(material.alpha_texopt), token);
2392
2
      continue;
2393
2
    }
2394
2395
    // displacement texture
2396
20.6k
    if (((0 == strncmp(token, "map_disp", 8)) ||
2397
20.6k
         (0 == strncmp(token, "map_Disp", 8))) &&
2398
177
        IS_SPACE(token[8])) {
2399
175
      token += 9;
2400
175
      ParseTextureNameAndOption(&(material.displacement_texname),
2401
175
                                &(material.displacement_texopt), token);
2402
175
      continue;
2403
175
    }
2404
2405
    // displacement texture
2406
20.4k
    if ((0 == strncmp(token, "disp", 4)) && IS_SPACE(token[4])) {
2407
38
      token += 5;
2408
38
      ParseTextureNameAndOption(&(material.displacement_texname),
2409
38
                                &(material.displacement_texopt), token);
2410
38
      continue;
2411
38
    }
2412
2413
    // reflection map
2414
20.4k
    if ((0 == strncmp(token, "refl", 4)) && IS_SPACE(token[4])) {
2415
40
      token += 5;
2416
40
      ParseTextureNameAndOption(&(material.reflection_texname),
2417
40
                                &(material.reflection_texopt), token);
2418
40
      continue;
2419
40
    }
2420
2421
    // PBR: roughness texture
2422
20.3k
    if ((0 == strncmp(token, "map_Pr", 6)) && IS_SPACE(token[6])) {
2423
0
      token += 7;
2424
0
      ParseTextureNameAndOption(&(material.roughness_texname),
2425
0
                                &(material.roughness_texopt), token);
2426
0
      continue;
2427
0
    }
2428
2429
    // PBR: metallic texture
2430
20.3k
    if ((0 == strncmp(token, "map_Pm", 6)) && IS_SPACE(token[6])) {
2431
81
      token += 7;
2432
81
      ParseTextureNameAndOption(&(material.metallic_texname),
2433
81
                                &(material.metallic_texopt), token);
2434
81
      continue;
2435
81
    }
2436
2437
    // PBR: sheen texture
2438
20.3k
    if ((0 == strncmp(token, "map_Ps", 6)) && IS_SPACE(token[6])) {
2439
12
      token += 7;
2440
12
      ParseTextureNameAndOption(&(material.sheen_texname),
2441
12
                                &(material.sheen_texopt), token);
2442
12
      continue;
2443
12
    }
2444
2445
    // PBR: emissive texture
2446
20.3k
    if ((0 == strncmp(token, "map_Ke", 6)) && IS_SPACE(token[6])) {
2447
0
      token += 7;
2448
0
      ParseTextureNameAndOption(&(material.emissive_texname),
2449
0
                                &(material.emissive_texopt), token);
2450
0
      continue;
2451
0
    }
2452
2453
    // PBR: normal map texture
2454
20.3k
    if ((0 == strncmp(token, "norm", 4)) && IS_SPACE(token[4])) {
2455
167
      token += 5;
2456
167
      ParseTextureNameAndOption(&(material.normal_texname),
2457
167
                                &(material.normal_texopt), token);
2458
167
      continue;
2459
167
    }
2460
2461
    // unknown parameter
2462
20.1k
    const char *_space = strchr(token, ' ');
2463
20.1k
    if (!_space) {
2464
13.2k
      _space = strchr(token, '\t');
2465
13.2k
    }
2466
20.1k
    if (_space) {
2467
9.42k
      std::ptrdiff_t len = _space - token;
2468
9.42k
      std::string key(token, static_cast<size_t>(len));
2469
9.42k
      std::string value = _space + 1;
2470
9.42k
      material.unknown_parameter.insert(
2471
9.42k
          std::pair<std::string, std::string>(key, value));
2472
9.42k
    }
2473
20.1k
  }
2474
  // flush last material.
2475
36
  material_map->insert(std::pair<std::string, int>(
2476
36
      material.name, static_cast<int>(materials->size())));
2477
36
  materials->push_back(material);
2478
2479
36
  if (warning) {
2480
36
    (*warning) = warn_ss.str();
2481
36
  }
2482
36
}
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
57
                                      std::string *warn, std::string *err) {
2547
57
  (void)err;
2548
57
  (void)matId;
2549
57
  if (!m_inStream) {
2550
21
    std::stringstream ss;
2551
21
    ss << "Material stream in error state. \n";
2552
21
    if (warn) {
2553
21
      (*warn) += ss.str();
2554
21
    }
2555
21
    return false;
2556
21
  }
2557
2558
36
  LoadMtl(matMap, materials, &m_inStream, warn, err);
2559
2560
36
  return true;
2561
57
}
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
43
             bool default_vcols_fallback) {
2604
43
  std::stringstream errss;
2605
2606
43
  std::vector<real_t> v;
2607
43
  std::vector<real_t> vertex_weights;  // optional [w] component in `v`
2608
43
  std::vector<real_t> vn;
2609
43
  std::vector<real_t> vt;
2610
43
  std::vector<real_t> vc;
2611
43
  std::vector<skin_weight_t> vw;  // tinyobj extension: vertex skin weights
2612
43
  std::vector<tag_t> tags;
2613
43
  PrimGroup prim_group;
2614
43
  std::string name;
2615
2616
  // material
2617
43
  std::set<std::string> material_filenames;
2618
43
  std::map<std::string, int> material_map;
2619
43
  int material = -1;
2620
2621
  // smoothing group id
2622
43
  unsigned int current_smoothing_id =
2623
43
      0;  // Initial value. 0 means no smoothing.
2624
2625
43
  int greatest_v_idx = -1;
2626
43
  int greatest_vn_idx = -1;
2627
43
  int greatest_vt_idx = -1;
2628
2629
43
  shape_t shape;
2630
2631
43
  bool found_all_colors = true;  // check if all 'v' line has color info
2632
2633
43
  size_t line_num = 0;
2634
43
  std::string linebuf;
2635
629
  while (inStream->peek() != -1) {
2636
586
    safeGetline(*inStream, linebuf);
2637
2638
586
    line_num++;
2639
2640
    // Trim newline '\r\n' or '\n'
2641
586
    if (linebuf.size() > 0) {
2642
582
      if (linebuf[linebuf.size() - 1] == '\n')
2643
0
        linebuf.erase(linebuf.size() - 1);
2644
582
    }
2645
586
    if (linebuf.size() > 0) {
2646
582
      if (linebuf[linebuf.size() - 1] == '\r')
2647
0
        linebuf.erase(linebuf.size() - 1);
2648
582
    }
2649
2650
    // Skip if empty line.
2651
586
    if (linebuf.empty()) {
2652
4
      continue;
2653
4
    }
2654
582
    if (line_num == 1) {
2655
40
      linebuf = removeUtf8Bom(linebuf);
2656
40
    }
2657
2658
    // Skip leading space.
2659
582
    const char *token = linebuf.c_str();
2660
582
    token += strspn(token, " \t");
2661
2662
582
    assert(token);
2663
582
    if (token[0] == '\0') continue;  // empty line
2664
2665
576
    if (token[0] == '#') continue;  // comment line
2666
2667
    // vertex
2668
573
    if (token[0] == 'v' && IS_SPACE((token[1]))) {
2669
6
      token += 2;
2670
6
      real_t x, y, z;
2671
6
      real_t r, g, b;
2672
2673
6
      int num_components = parseVertexWithColor(&x, &y, &z, &r, &g, &b, &token);
2674
6
      found_all_colors &= (num_components == 6);
2675
2676
6
      v.push_back(x);
2677
6
      v.push_back(y);
2678
6
      v.push_back(z);
2679
2680
6
      vertex_weights.push_back(
2681
6
          r);  // r = w, and initialized to 1.0 when `w` component is not found.
2682
2683
6
      if ((num_components == 6) || default_vcols_fallback) {
2684
6
        vc.push_back(r);
2685
6
        vc.push_back(g);
2686
6
        vc.push_back(b);
2687
6
      }
2688
2689
6
      continue;
2690
6
    }
2691
2692
    // normal
2693
567
    if (token[0] == 'v' && token[1] == 'n' && IS_SPACE((token[2]))) {
2694
63
      token += 3;
2695
63
      real_t x, y, z;
2696
63
      parseReal3(&x, &y, &z, &token);
2697
63
      vn.push_back(x);
2698
63
      vn.push_back(y);
2699
63
      vn.push_back(z);
2700
63
      continue;
2701
63
    }
2702
2703
    // texcoord
2704
504
    if (token[0] == 'v' && token[1] == 't' && IS_SPACE((token[2]))) {
2705
1
      token += 3;
2706
1
      real_t x, y;
2707
1
      parseReal2(&x, &y, &token);
2708
1
      vt.push_back(x);
2709
1
      vt.push_back(y);
2710
1
      continue;
2711
1
    }
2712
2713
    // skin weight. tinyobj extension
2714
503
    if (token[0] == 'v' && token[1] == 'w' && IS_SPACE((token[2]))) {
2715
0
      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
0
      int vid = 0;
2723
0
      vid = parseInt(&token);
2724
2725
0
      skin_weight_t sw;
2726
2727
0
      sw.vertex_id = vid;
2728
2729
0
      while (!IS_NEW_LINE(token[0]) && token[0] != '#') {
2730
0
        real_t j, w;
2731
        // joint_id should not be negative, weight may be negative
2732
        // TODO(syoyo): # of elements check
2733
0
        parseReal2(&j, &w, &token, -1.0);
2734
2735
0
        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
0
        joint_and_weight_t jw;
2747
2748
0
        jw.joint_id = int(j);
2749
0
        jw.weight = w;
2750
2751
0
        sw.weightValues.push_back(jw);
2752
2753
0
        size_t n = strspn(token, " \t\r");
2754
0
        token += n;
2755
0
      }
2756
2757
0
      vw.push_back(sw);
2758
0
    }
2759
2760
503
    warning_context context;
2761
503
    context.warn = warn;
2762
503
    context.line_number = line_num;
2763
2764
    // line
2765
503
    if (token[0] == 'l' && IS_SPACE((token[1]))) {
2766
4
      token += 2;
2767
2768
4
      __line_t line;
2769
2770
10
      while (!IS_NEW_LINE(token[0]) && token[0] != '#') {
2771
6
        vertex_index_t vi;
2772
6
        if (!parseTriple(&token, static_cast<int>(v.size() / 3),
2773
6
                         static_cast<int>(vn.size() / 3),
2774
6
                         static_cast<int>(vt.size() / 2), &vi, context)) {
2775
0
          if (err) {
2776
0
            (*err) +=
2777
0
                "Failed to parse `l' line (e.g. a zero value for vertex index. "
2778
0
                "Line " +
2779
0
                toString(line_num) + ").\n";
2780
0
          }
2781
0
          return false;
2782
0
        }
2783
2784
6
        line.vertex_indices.push_back(vi);
2785
2786
6
        size_t n = strspn(token, " \t\r");
2787
6
        token += n;
2788
6
      }
2789
2790
4
      prim_group.lineGroup.push_back(line);
2791
2792
4
      continue;
2793
4
    }
2794
2795
    // points
2796
499
    if (token[0] == 'p' && IS_SPACE((token[1]))) {
2797
4
      token += 2;
2798
2799
4
      __points_t pts;
2800
2801
8
      while (!IS_NEW_LINE(token[0]) && token[0] != '#') {
2802
4
        vertex_index_t vi;
2803
4
        if (!parseTriple(&token, static_cast<int>(v.size() / 3),
2804
4
                         static_cast<int>(vn.size() / 3),
2805
4
                         static_cast<int>(vt.size() / 2), &vi, context)) {
2806
0
          if (err) {
2807
0
            (*err) +=
2808
0
                "Failed to parse `p' line (e.g. a zero value for vertex index. "
2809
0
                "Line " +
2810
0
                toString(line_num) + ").\n";
2811
0
          }
2812
0
          return false;
2813
0
        }
2814
2815
4
        pts.vertex_indices.push_back(vi);
2816
2817
4
        size_t n = strspn(token, " \t\r");
2818
4
        token += n;
2819
4
      }
2820
2821
4
      prim_group.pointsGroup.push_back(pts);
2822
2823
4
      continue;
2824
4
    }
2825
2826
    // face
2827
495
    if (token[0] == 'f' && IS_SPACE((token[1]))) {
2828
18
      token += 2;
2829
18
      token += strspn(token, " \t");
2830
2831
18
      face_t face;
2832
2833
18
      face.smoothing_group_id = current_smoothing_id;
2834
18
      face.vertex_indices.reserve(3);
2835
2836
80
      while (!IS_NEW_LINE(token[0]) && token[0] != '#') {
2837
62
        vertex_index_t vi;
2838
62
        if (!parseTriple(&token, static_cast<int>(v.size() / 3),
2839
62
                         static_cast<int>(vn.size() / 3),
2840
62
                         static_cast<int>(vt.size() / 2), &vi, context)) {
2841
0
          if (err) {
2842
0
            (*err) +=
2843
0
                "Failed to parse `f' line (e.g. a zero value for vertex index "
2844
0
                "or invalid relative vertex index). Line " +
2845
0
                toString(line_num) + ").\n";
2846
0
          }
2847
0
          return false;
2848
0
        }
2849
2850
62
        greatest_v_idx = greatest_v_idx > vi.v_idx ? greatest_v_idx : vi.v_idx;
2851
62
        greatest_vn_idx =
2852
62
            greatest_vn_idx > vi.vn_idx ? greatest_vn_idx : vi.vn_idx;
2853
62
        greatest_vt_idx =
2854
62
            greatest_vt_idx > vi.vt_idx ? greatest_vt_idx : vi.vt_idx;
2855
2856
62
        face.vertex_indices.push_back(vi);
2857
62
        size_t n = strspn(token, " \t\r");
2858
62
        token += n;
2859
62
      }
2860
2861
      // replace with emplace_back + std::move on C++11
2862
18
      prim_group.faceGroup.push_back(face);
2863
2864
18
      continue;
2865
18
    }
2866
2867
    // use mtl
2868
477
    if ((0 == strncmp(token, "usemtl", 6))) {
2869
23
      token += 6;
2870
23
      std::string namebuf = parseString(&token);
2871
2872
23
      int newMaterialId = -1;
2873
23
      std::map<std::string, int>::const_iterator it =
2874
23
          material_map.find(namebuf);
2875
23
      if (it != material_map.end()) {
2876
11
        newMaterialId = it->second;
2877
12
      } else {
2878
        // { error!! material not found }
2879
12
        if (warn) {
2880
12
          (*warn) += "material [ '" + namebuf + "' ] not found in .mtl\n";
2881
12
        }
2882
12
      }
2883
2884
23
      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
15
        exportGroupsToShape(&shape, prim_group, tags, material, name,
2889
15
                            triangulate, v, warn);
2890
15
        prim_group.faceGroup.clear();
2891
15
        material = newMaterialId;
2892
15
      }
2893
2894
23
      continue;
2895
23
    }
2896
2897
    // load mtl
2898
454
    if ((0 == strncmp(token, "mtllib", 6)) && IS_SPACE((token[6]))) {
2899
53
      if (readMatFn) {
2900
53
        token += 7;
2901
2902
53
        std::vector<std::string> filenames;
2903
53
        SplitString(std::string(token), ' ', '\\', filenames);
2904
2905
53
        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
53
        } else {
2915
53
          bool found = false;
2916
85
          for (size_t s = 0; s < filenames.size(); s++) {
2917
68
            if (material_filenames.count(filenames[s]) > 0) {
2918
11
              found = true;
2919
11
              continue;
2920
11
            }
2921
2922
57
            std::string warn_mtl;
2923
57
            std::string err_mtl;
2924
57
            bool ok = (*readMatFn)(filenames[s].c_str(), materials,
2925
57
                                   &material_map, &warn_mtl, &err_mtl);
2926
57
            if (warn && (!warn_mtl.empty())) {
2927
23
              (*warn) += warn_mtl;
2928
23
            }
2929
2930
57
            if (err && (!err_mtl.empty())) {
2931
0
              (*err) += err_mtl;
2932
0
            }
2933
2934
57
            if (ok) {
2935
36
              found = true;
2936
36
              material_filenames.insert(filenames[s]);
2937
36
              break;
2938
36
            }
2939
57
          }
2940
2941
53
          if (!found) {
2942
6
            if (warn) {
2943
6
              (*warn) +=
2944
6
                  "Failed to load material file(s). Use default "
2945
6
                  "material.\n";
2946
6
            }
2947
6
          }
2948
53
        }
2949
53
      }
2950
2951
53
      continue;
2952
53
    }
2953
2954
    // group name
2955
401
    if (token[0] == 'g' && IS_SPACE((token[1]))) {
2956
      // flush previous face group.
2957
3
      bool ret = exportGroupsToShape(&shape, prim_group, tags, material, name,
2958
3
                                     triangulate, v, warn);
2959
3
      (void)ret;  // return value not used.
2960
2961
3
      if (shape.mesh.indices.size() > 0) {
2962
0
        shapes->push_back(shape);
2963
0
      }
2964
2965
3
      shape = shape_t();
2966
2967
      // material = -1;
2968
3
      prim_group.clear();
2969
2970
3
      std::vector<std::string> names;
2971
2972
13
      while (!IS_NEW_LINE(token[0]) && token[0] != '#') {
2973
10
        std::string str = parseString(&token);
2974
10
        names.push_back(str);
2975
10
        token += strspn(token, " \t\r");  // skip tag
2976
10
      }
2977
2978
      // names[0] must be 'g'
2979
2980
3
      if (names.size() < 2) {
2981
        // 'g' with empty names
2982
1
        if (warn) {
2983
1
          std::stringstream ss;
2984
1
          ss << "Empty group name. line: " << line_num << "\n";
2985
1
          (*warn) += ss.str();
2986
1
          name = "";
2987
1
        }
2988
2
      } else {
2989
2
        std::stringstream ss;
2990
2
        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
7
        for (size_t i = 2; i < names.size(); i++) {
2997
5
          ss << " " << names[i];
2998
5
        }
2999
3000
2
        name = ss.str();
3001
2
      }
3002
3003
3
      continue;
3004
3
    }
3005
3006
    // object name
3007
398
    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
2
        shapes->push_back(shape);
3016
2
      }
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
396
    if (token[0] == 't' && IS_SPACE(token[1])) {
3032
29
      const int max_tag_nums = 8192;  // FIXME(syoyo): Parameterize.
3033
29
      tag_t tag;
3034
3035
29
      token += 2;
3036
3037
29
      tag.name = parseString(&token);
3038
3039
29
      tag_sizes ts = parseTagTriple(&token);
3040
3041
29
      if (ts.num_ints < 0) {
3042
0
        ts.num_ints = 0;
3043
0
      }
3044
29
      if (ts.num_ints > max_tag_nums) {
3045
2
        ts.num_ints = max_tag_nums;
3046
2
      }
3047
3048
29
      if (ts.num_reals < 0) {
3049
12
        ts.num_reals = 0;
3050
12
      }
3051
29
      if (ts.num_reals > max_tag_nums) {
3052
0
        ts.num_reals = max_tag_nums;
3053
0
      }
3054
3055
29
      if (ts.num_strings < 0) {
3056
0
        ts.num_strings = 0;
3057
0
      }
3058
29
      if (ts.num_strings > max_tag_nums) {
3059
0
        ts.num_strings = max_tag_nums;
3060
0
      }
3061
3062
29
      tag.intValues.resize(static_cast<size_t>(ts.num_ints));
3063
3064
16.4k
      for (size_t i = 0; i < static_cast<size_t>(ts.num_ints); ++i) {
3065
16.4k
        tag.intValues[i] = parseInt(&token);
3066
16.4k
      }
3067
3068
29
      tag.floatValues.resize(static_cast<size_t>(ts.num_reals));
3069
25.6k
      for (size_t i = 0; i < static_cast<size_t>(ts.num_reals); ++i) {
3070
25.5k
        tag.floatValues[i] = parseReal(&token);
3071
25.5k
      }
3072
3073
29
      tag.stringValues.resize(static_cast<size_t>(ts.num_strings));
3074
29
      for (size_t i = 0; i < static_cast<size_t>(ts.num_strings); ++i) {
3075
0
        tag.stringValues[i] = parseString(&token);
3076
0
      }
3077
3078
29
      tags.push_back(tag);
3079
3080
29
      continue;
3081
29
    }
3082
3083
367
    if (token[0] == 's' && IS_SPACE(token[1])) {
3084
      // smoothing group id
3085
3
      token += 2;
3086
3087
      // skip space.
3088
3
      token += strspn(token, " \t");  // skip space
3089
3090
3
      if (token[0] == '\0') {
3091
2
        continue;
3092
2
      }
3093
3094
1
      if (token[0] == '\r' || token[1] == '\n') {
3095
0
        continue;
3096
0
      }
3097
3098
1
      if (strlen(token) >= 3 && token[0] == 'o' && token[1] == 'f' &&
3099
0
          token[2] == 'f') {
3100
0
        current_smoothing_id = 0;
3101
1
      } else {
3102
        // assume number
3103
1
        int smGroupId = parseInt(&token);
3104
1
        if (smGroupId < 0) {
3105
          // parse error. force set to 0.
3106
          // FIXME(syoyo): Report warning.
3107
0
          current_smoothing_id = 0;
3108
1
        } else {
3109
1
          current_smoothing_id = static_cast<unsigned int>(smGroupId);
3110
1
        }
3111
1
      }
3112
3113
1
      continue;
3114
1
    }  // smoothing group id
3115
3116
    // Ignore unknown command.
3117
367
  }
3118
3119
  // not all vertices have colors, no default colors desired? -> clear colors
3120
43
  if (!found_all_colors && !default_vcols_fallback) {
3121
0
    vc.clear();
3122
0
  }
3123
3124
43
  if (greatest_v_idx >= static_cast<int>(v.size() / 3)) {
3125
2
    if (warn) {
3126
2
      std::stringstream ss;
3127
2
      ss << "Vertex indices out of bounds (line " << line_num << ".)\n\n";
3128
2
      (*warn) += ss.str();
3129
2
    }
3130
2
  }
3131
43
  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
43
  if (greatest_vt_idx >= static_cast<int>(vt.size() / 2)) {
3140
1
    if (warn) {
3141
1
      std::stringstream ss;
3142
1
      ss << "Vertex texcoord indices out of bounds (line " << line_num
3143
1
         << ".)\n\n";
3144
1
      (*warn) += ss.str();
3145
1
    }
3146
1
  }
3147
3148
43
  bool ret = exportGroupsToShape(&shape, prim_group, tags, material, name,
3149
43
                                 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
43
  if (ret || shape.mesh.indices
3155
39
                 .size()) {  // FIXME(syoyo): Support other prims(e.g. lines)
3156
4
    shapes->push_back(shape);
3157
4
  }
3158
43
  prim_group.clear();  // for safety
3159
3160
43
  if (err) {
3161
43
    (*err) += errss.str();
3162
43
  }
3163
3164
43
  attrib->vertices.swap(v);
3165
43
  attrib->vertex_weights.swap(vertex_weights);
3166
43
  attrib->normals.swap(vn);
3167
43
  attrib->texcoords.swap(vt);
3168
43
  attrib->texcoord_ws.swap(vt);
3169
43
  attrib->colors.swap(vc);
3170
43
  attrib->skin_weights.swap(vw);
3171
3172
43
  return true;
3173
43
}
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
43
                                const ObjReaderConfig &config) {
3498
43
  std::stringbuf obj_buf(obj_text);
3499
43
  std::stringbuf mtl_buf(mtl_text);
3500
3501
43
  std::istream obj_ifs(&obj_buf);
3502
43
  std::istream mtl_ifs(&mtl_buf);
3503
3504
43
  MaterialStreamReader mtl_ss(mtl_ifs);
3505
3506
43
  valid_ = LoadObj(&attrib_, &shapes_, &materials_, &warning_, &error_,
3507
43
                   &obj_ifs, &mtl_ss, config.triangulate, config.vertex_color);
3508
3509
43
  return valid_;
3510
43
}
3511
3512
#ifdef __clang__
3513
#pragma clang diagnostic pop
3514
#endif
3515
}  // namespace tinyobj
3516
3517
#endif