Coverage Report

Created: 2026-01-17 06:05

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