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