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