Clutter Engine 0.0.1
Loading...
Searching...
No Matches
tiny_obj_loader.h
1/*
2The MIT License (MIT)
3
4Copyright (c) 2012-2016 Syoyo Fujita and many contributors.
5
6Permission is hereby granted, free of charge, to any person obtaining a copy
7of this software and associated documentation files (the "Software"), to deal
8in the Software without restriction, including without limitation the rights
9to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10copies of the Software, and to permit persons to whom the Software is
11furnished to do so, subject to the following conditions:
12
13The above copyright notice and this permission notice shall be included in
14all copies or substantial portions of the Software.
15
16THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22THE SOFTWARE.
23*/
24
25//
26// version 1.0.6 : Add TINYOBJLOADER_USE_DOUBLE option(#124)
27// version 1.0.5 : Ignore `Tr` when `d` exists in MTL(#43)
28// version 1.0.4 : Support multiple filenames for 'mtllib'(#112)
29// version 1.0.3 : Support parsing texture options(#85)
30// version 1.0.2 : Improve parsing speed by about a factor of 2 for large
31// files(#105)
32// version 1.0.1 : Fixes a shape is lost if obj ends with a 'usemtl'(#104)
33// version 1.0.0 : Change data structure. Change license from BSD to MIT.
34//
35
36//
37// Use this in *one* .cc
38// #define TINYOBJLOADER_IMPLEMENTATION
39// #include "tiny_obj_loader.h"
40//
41
42#ifndef TINY_OBJ_LOADER_H_
43#define TINY_OBJ_LOADER_H_
44
45#include <map>
46#include <string>
47#include <vector>
48
49namespace tinyobj {
50
51// https://en.wikipedia.org/wiki/Wavefront_.obj_file says ...
52//
53// -blendu on | off # set horizontal texture blending
54// (default on)
55// -blendv on | off # set vertical texture blending
56// (default on)
57// -boost real_value # boost mip-map sharpness
58// -mm base_value gain_value # modify texture map values (default
59// 0 1)
60// # base_value = brightness,
61// gain_value = contrast
62// -o u [v [w]] # Origin offset (default
63// 0 0 0)
64// -s u [v [w]] # Scale (default
65// 1 1 1)
66// -t u [v [w]] # Turbulence (default
67// 0 0 0)
68// -texres resolution # texture resolution to create
69// -clamp on | off # only render texels in the clamped
70// 0-1 range (default off)
71// # When unclamped, textures are
72// repeated across a surface,
73// # when clamped, only texels which
74// fall within the 0-1
75// # range are rendered.
76// -bm mult_value # bump multiplier (for bump maps
77// only)
78//
79// -imfchan r | g | b | m | l | z # specifies which channel of the file
80// is used to
81// # create a scalar or bump texture.
82// r:red, g:green,
83// # b:blue, m:matte, l:luminance,
84// z:z-depth..
85// # (the default for bump is 'l' and
86// for decal is 'm')
87// bump -imfchan r bumpmap.tga # says to use the red channel of
88// bumpmap.tga as the bumpmap
89//
90// For reflection maps...
91//
92// -type sphere # specifies a sphere for a "refl"
93// reflection map
94// -type cube_top | cube_bottom | # when using a cube map, the texture
95// file for each
96// cube_front | cube_back | # side of the cube is specified
97// separately
98// cube_left | cube_right
99
100#ifdef TINYOBJLOADER_USE_DOUBLE
101 //#pragma message "using double"
102 typedef double real_t;
103#else
104 //#pragma message "using float"
105 typedef float real_t;
106#endif
107
108typedef enum {
109 TEXTURE_TYPE_NONE, // default
110 TEXTURE_TYPE_SPHERE,
111 TEXTURE_TYPE_CUBE_TOP,
112 TEXTURE_TYPE_CUBE_BOTTOM,
113 TEXTURE_TYPE_CUBE_FRONT,
114 TEXTURE_TYPE_CUBE_BACK,
115 TEXTURE_TYPE_CUBE_LEFT,
116 TEXTURE_TYPE_CUBE_RIGHT
117} texture_type_t;
118
119typedef struct {
120 texture_type_t type; // -type (default TEXTURE_TYPE_NONE)
121 real_t sharpness; // -boost (default 1.0?)
122 real_t brightness; // base_value in -mm option (default 0)
123 real_t contrast; // gain_value in -mm option (default 1)
124 real_t origin_offset[3]; // -o u [v [w]] (default 0 0 0)
125 real_t scale[3]; // -s u [v [w]] (default 1 1 1)
126 real_t turbulence[3]; // -t u [v [w]] (default 0 0 0)
127 // int texture_resolution; // -texres resolution (default = ?) TODO
128 bool clamp; // -clamp (default false)
129 char imfchan; // -imfchan (the default for bump is 'l' and for decal is 'm')
130 bool blendu; // -blendu (default on)
131 bool blendv; // -blendv (default on)
132 real_t bump_multiplier; // -bm (for bump maps only, default 1.0)
134
135typedef struct {
136 std::string name;
137
138 real_t ambient[3];
139 real_t diffuse[3];
140 real_t specular[3];
141 real_t transmittance[3];
142 real_t emission[3];
143 real_t shininess;
144 real_t ior; // index of refraction
145 real_t dissolve; // 1 == opaque; 0 == fully transparent
146 // illumination model (see http://www.fileformat.info/format/material/)
147 int illum;
148
149 int dummy; // Suppress padding warning.
150
151 std::string ambient_texname; // map_Ka
152 std::string diffuse_texname; // map_Kd
153 std::string specular_texname; // map_Ks
154 std::string specular_highlight_texname; // map_Ns
155 std::string bump_texname; // map_bump, bump
156 std::string displacement_texname; // disp
157 std::string alpha_texname; // map_d
158
159 texture_option_t ambient_texopt;
160 texture_option_t diffuse_texopt;
161 texture_option_t specular_texopt;
162 texture_option_t specular_highlight_texopt;
163 texture_option_t bump_texopt;
164 texture_option_t displacement_texopt;
165 texture_option_t alpha_texopt;
166
167 // PBR extension
168 // http://exocortex.com/blog/extending_wavefront_mtl_to_support_pbr
169 real_t roughness; // [0, 1] default 0
170 real_t metallic; // [0, 1] default 0
171 real_t sheen; // [0, 1] default 0
172 real_t clearcoat_thickness; // [0, 1] default 0
173 real_t clearcoat_roughness; // [0, 1] default 0
174 real_t anisotropy; // aniso. [0, 1] default 0
175 real_t anisotropy_rotation; // anisor. [0, 1] default 0
176 real_t pad0;
177 real_t pad1;
178 std::string roughness_texname; // map_Pr
179 std::string metallic_texname; // map_Pm
180 std::string sheen_texname; // map_Ps
181 std::string emissive_texname; // map_Ke
182 std::string normal_texname; // norm. For normal mapping.
183
184 texture_option_t roughness_texopt;
185 texture_option_t metallic_texopt;
186 texture_option_t sheen_texopt;
187 texture_option_t emissive_texopt;
188 texture_option_t normal_texopt;
189
190 int pad2;
191
192 std::map<std::string, std::string> unknown_parameter;
193} material_t;
194
195typedef struct {
196 std::string name;
197
198 std::vector<int> intValues;
199 std::vector<real_t> floatValues;
200 std::vector<std::string> stringValues;
201} tag_t;
202
203// Index struct to support different indices for vtx/normal/texcoord.
204// -1 means not used.
205typedef struct {
206 int vertex_index;
207 int normal_index;
208 int texcoord_index;
209} index_t;
210
211typedef struct {
212 std::vector<index_t> indices;
213 std::vector<unsigned char> num_face_vertices; // The number of vertices per
214 // face. 3 = polygon, 4 = quad,
215 // ... Up to 255.
216 std::vector<int> material_ids; // per-face material ID
217 std::vector<tag_t> tags; // SubD tag
218} mesh_t;
219
220typedef struct {
221 std::string name;
222 mesh_t mesh;
223} shape_t;
224
225// Vertex attributes
226typedef struct {
227 std::vector<real_t> vertices; // 'v'
228 std::vector<real_t> normals; // 'vn'
229 std::vector<real_t> texcoords; // 'vt'
230} attrib_t;
231
232typedef struct callback_t_ {
233 // W is optional and set to 1 if there is no `w` item in `v` line
234 void (*vertex_cb)(void *user_data, real_t x, real_t y, real_t z, real_t w);
235 void (*normal_cb)(void *user_data, real_t x, real_t y, real_t z);
236
237 // y and z are optional and set to 0 if there is no `y` and/or `z` item(s) in
238 // `vt` line.
239 void (*texcoord_cb)(void *user_data, real_t x, real_t y, real_t z);
240
241 // called per 'f' line. num_indices is the number of face indices(e.g. 3 for
242 // triangle, 4 for quad)
243 // 0 will be passed for undefined index in index_t members.
244 void (*index_cb)(void *user_data, index_t *indices, int num_indices);
245 // `name` material name, `material_id` = the array index of material_t[]. -1
246 // if
247 // a material not found in .mtl
248 void (*usemtl_cb)(void *user_data, const char *name, int material_id);
249 // `materials` = parsed material data.
250 void (*mtllib_cb)(void *user_data, const material_t *materials,
251 int num_materials);
252 // There may be multiple group names
253 void (*group_cb)(void *user_data, const char **names, int num_names);
254 void (*object_cb)(void *user_data, const char *name);
255
257 : vertex_cb(NULL),
258 normal_cb(NULL),
259 texcoord_cb(NULL),
260 index_cb(NULL),
261 usemtl_cb(NULL),
262 mtllib_cb(NULL),
263 group_cb(NULL),
264 object_cb(NULL) {}
265} callback_t;
266
268 public:
269 MaterialReader() {}
270 virtual ~MaterialReader();
271
272 virtual bool operator()(const std::string &matId,
273 std::vector<material_t> *materials,
274 std::map<std::string, int> *matMap,
275 std::string *err) = 0;
276};
277
279 public:
280 explicit MaterialFileReader(const std::string &mtl_basedir)
281 : m_mtlBaseDir(mtl_basedir) {}
282 virtual ~MaterialFileReader() {}
283 virtual bool operator()(const std::string &matId,
284 std::vector<material_t> *materials,
285 std::map<std::string, int> *matMap, std::string *err);
286
287 private:
288 std::string m_mtlBaseDir;
289};
290
292 public:
293 explicit MaterialStreamReader(std::istream &inStream)
294 : m_inStream(inStream) {}
295 virtual ~MaterialStreamReader() {}
296 virtual bool operator()(const std::string &matId,
297 std::vector<material_t> *materials,
298 std::map<std::string, int> *matMap, std::string *err);
299
300 private:
301 std::istream &m_inStream;
302};
303
314bool LoadObj(attrib_t *attrib, std::vector<shape_t> *shapes,
315 std::vector<material_t> *materials, std::string *err,
316 const char *filename, const char *mtl_basedir = NULL,
317 bool triangulate = true);
318
325bool LoadObjWithCallback(std::istream &inStream, const callback_t &callback,
326 void *user_data = NULL,
327 MaterialReader *readMatFn = NULL,
328 std::string *err = NULL);
329
334bool LoadObj(attrib_t *attrib, std::vector<shape_t> *shapes,
335 std::vector<material_t> *materials, std::string *err,
336 std::istream *inStream, MaterialReader *readMatFn = NULL,
337 bool triangulate = true);
338
340void LoadMtl(std::map<std::string, int> *material_map,
341 std::vector<material_t> *materials, std::istream *inStream,
342 std::string *warning);
343
344} // namespace tinyobj
345
346#endif // TINY_OBJ_LOADER_H_
347
348#ifdef TINYOBJLOADER_IMPLEMENTATION
349#include <cassert>
350#include <cctype>
351#include <cmath>
352#include <cstddef>
353#include <cstdlib>
354#include <cstring>
355#include <utility>
356
357#include <fstream>
358#include <sstream>
359
360namespace tinyobj {
361
362MaterialReader::~MaterialReader() {}
363
364#define TINYOBJ_SSCANF_BUFFER_SIZE (4096)
365
366struct vertex_index {
367 int v_idx, vt_idx, vn_idx;
368 vertex_index() : v_idx(-1), vt_idx(-1), vn_idx(-1) {}
369 explicit vertex_index(int idx) : v_idx(idx), vt_idx(idx), vn_idx(idx) {}
370 vertex_index(int vidx, int vtidx, int vnidx)
371 : v_idx(vidx), vt_idx(vtidx), vn_idx(vnidx) {}
372};
373
374struct tag_sizes {
375 tag_sizes() : num_ints(0), num_reals(0), num_strings(0) {}
376 int num_ints;
377 int num_reals;
378 int num_strings;
379};
380
381struct obj_shape {
382 std::vector<real_t> v;
383 std::vector<real_t> vn;
384 std::vector<real_t> vt;
385};
386
387// See
388// http://stackoverflow.com/questions/6089231/getting-std-ifstream-to-handle-lf-cr-and-crlf
389static std::istream &safeGetline(std::istream &is, std::string &t) {
390 t.clear();
391
392 // The characters in the stream are read one-by-one using a std::streambuf.
393 // That is faster than reading them one-by-one using the std::istream.
394 // Code that uses streambuf this way must be guarded by a sentry object.
395 // The sentry object performs various tasks,
396 // such as thread synchronization and updating the stream state.
397
398 std::istream::sentry se(is, true);
399 std::streambuf *sb = is.rdbuf();
400
401 for (;;) {
402 int c = sb->sbumpc();
403 switch (c) {
404 case '\n':
405 return is;
406 case '\r':
407 if (sb->sgetc() == '\n') sb->sbumpc();
408 return is;
409 case EOF:
410 // Also handle the case when the last line has no line ending
411 if (t.empty()) is.setstate(std::ios::eofbit);
412 return is;
413 default:
414 t += static_cast<char>(c);
415 }
416 }
417}
418
419#define IS_SPACE(x) (((x) == ' ') || ((x) == '\t'))
420#define IS_DIGIT(x) \
421 (static_cast<unsigned int>((x) - '0') < static_cast<unsigned int>(10))
422#define IS_NEW_LINE(x) (((x) == '\r') || ((x) == '\n') || ((x) == '\0'))
423
424// Make index zero-base, and also support relative index.
425static inline int fixIndex(int idx, int n) {
426 if (idx > 0) return idx - 1;
427 if (idx == 0) return 0;
428 return n + idx; // negative value = relative
429}
430
431static inline std::string parseString(const char **token) {
432 std::string s;
433 (*token) += strspn((*token), " \t");
434 size_t e = strcspn((*token), " \t\r");
435 s = std::string((*token), &(*token)[e]);
436 (*token) += e;
437 return s;
438}
439
440static inline int parseInt(const char **token) {
441 (*token) += strspn((*token), " \t");
442 int i = atoi((*token));
443 (*token) += strcspn((*token), " \t\r");
444 return i;
445}
446
447// Tries to parse a floating point number located at s.
448//
449// s_end should be a location in the string where reading should absolutely
450// stop. For example at the end of the string, to prevent buffer overflows.
451//
452// Parses the following EBNF grammar:
453// sign = "+" | "-" ;
454// END = ? anything not in digit ?
455// digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" ;
456// integer = [sign] , digit , {digit} ;
457// decimal = integer , ["." , integer] ;
458// float = ( decimal , END ) | ( decimal , ("E" | "e") , integer , END ) ;
459//
460// Valid strings are for example:
461// -0 +3.1417e+2 -0.0E-3 1.0324 -1.41 11e2
462//
463// If the parsing is a success, result is set to the parsed value and true
464// is returned.
465//
466// The function is greedy and will parse until any of the following happens:
467// - a non-conforming character is encountered.
468// - s_end is reached.
469//
470// The following situations triggers a failure:
471// - s >= s_end.
472// - parse failure.
473//
474static bool tryParseDouble(const char *s, const char *s_end, double *result) {
475 if (s >= s_end) {
476 return false;
477 }
478
479 double mantissa = 0.0;
480 // This exponent is base 2 rather than 10.
481 // However the exponent we parse is supposed to be one of ten,
482 // thus we must take care to convert the exponent/and or the
483 // mantissa to a * 2^E, where a is the mantissa and E is the
484 // exponent.
485 // To get the final double we will use ldexp, it requires the
486 // exponent to be in base 2.
487 int exponent = 0;
488
489 // NOTE: THESE MUST BE DECLARED HERE SINCE WE ARE NOT ALLOWED
490 // TO JUMP OVER DEFINITIONS.
491 char sign = '+';
492 char exp_sign = '+';
493 char const *curr = s;
494
495 // How many characters were read in a loop.
496 int read = 0;
497 // Tells whether a loop terminated due to reaching s_end.
498 bool end_not_reached = false;
499
500 /*
501 BEGIN PARSING.
502 */
503
504 // Find out what sign we've got.
505 if (*curr == '+' || *curr == '-') {
506 sign = *curr;
507 curr++;
508 } else if (IS_DIGIT(*curr)) { /* Pass through. */
509 } else {
510 goto fail;
511 }
512
513 // Read the integer part.
514 end_not_reached = (curr != s_end);
515 while (end_not_reached && IS_DIGIT(*curr)) {
516 mantissa *= 10;
517 mantissa += static_cast<int>(*curr - 0x30);
518 curr++;
519 read++;
520 end_not_reached = (curr != s_end);
521 }
522
523 // We must make sure we actually got something.
524 if (read == 0) goto fail;
525 // We allow numbers of form "#", "###" etc.
526 if (!end_not_reached) goto assemble;
527
528 // Read the decimal part.
529 if (*curr == '.') {
530 curr++;
531 read = 1;
532 end_not_reached = (curr != s_end);
533 while (end_not_reached && IS_DIGIT(*curr)) {
534 static const double pow_lut[] = {
535 1.0, 0.1, 0.01, 0.001, 0.0001, 0.00001, 0.000001, 0.0000001,
536 };
537 const int lut_entries = sizeof pow_lut / sizeof pow_lut[0];
538
539 // NOTE: Don't use powf here, it will absolutely murder precision.
540 mantissa += static_cast<int>(*curr - 0x30) *
541 (read < lut_entries ? pow_lut[read] : std::pow(10.0, -read));
542 read++;
543 curr++;
544 end_not_reached = (curr != s_end);
545 }
546 } else if (*curr == 'e' || *curr == 'E') {
547 } else {
548 goto assemble;
549 }
550
551 if (!end_not_reached) goto assemble;
552
553 // Read the exponent part.
554 if (*curr == 'e' || *curr == 'E') {
555 curr++;
556 // Figure out if a sign is present and if it is.
557 end_not_reached = (curr != s_end);
558 if (end_not_reached && (*curr == '+' || *curr == '-')) {
559 exp_sign = *curr;
560 curr++;
561 } else if (IS_DIGIT(*curr)) { /* Pass through. */
562 } else {
563 // Empty E is not allowed.
564 goto fail;
565 }
566
567 read = 0;
568 end_not_reached = (curr != s_end);
569 while (end_not_reached && IS_DIGIT(*curr)) {
570 exponent *= 10;
571 exponent += static_cast<int>(*curr - 0x30);
572 curr++;
573 read++;
574 end_not_reached = (curr != s_end);
575 }
576 exponent *= (exp_sign == '+' ? 1 : -1);
577 if (read == 0) goto fail;
578 }
579
580assemble:
581 *result =
582 (sign == '+' ? 1 : -1) *
583 (exponent ? std::ldexp(mantissa * std::pow(5.0, exponent), exponent) : mantissa);
584 return true;
585fail:
586 return false;
587}
588
589static inline real_t parseReal(const char **token, double default_value = 0.0) {
590 (*token) += strspn((*token), " \t");
591 const char *end = (*token) + strcspn((*token), " \t\r");
592 double val = default_value;
593 tryParseDouble((*token), end, &val);
594 real_t f = static_cast<real_t>(val);
595 (*token) = end;
596 return f;
597}
598
599static inline void parseReal2(real_t *x, real_t *y, const char **token,
600 const double default_x = 0.0,
601 const double default_y = 0.0) {
602 (*x) = parseReal(token, default_x);
603 (*y) = parseReal(token, default_y);
604}
605
606static inline void parseReal3(real_t *x, real_t *y, real_t *z, const char **token,
607 const double default_x = 0.0,
608 const double default_y = 0.0,
609 const double default_z = 0.0) {
610 (*x) = parseReal(token, default_x);
611 (*y) = parseReal(token, default_y);
612 (*z) = parseReal(token, default_z);
613}
614
615static inline void parseV(real_t *x, real_t *y, real_t *z, real_t *w,
616 const char **token, const double default_x = 0.0,
617 const double default_y = 0.0,
618 const double default_z = 0.0,
619 const double default_w = 1.0) {
620 (*x) = parseReal(token, default_x);
621 (*y) = parseReal(token, default_y);
622 (*z) = parseReal(token, default_z);
623 (*w) = parseReal(token, default_w);
624}
625
626static inline bool parseOnOff(const char **token, bool default_value = true) {
627 (*token) += strspn((*token), " \t");
628 const char *end = (*token) + strcspn((*token), " \t\r");
629
630 bool ret = default_value;
631 if ((0 == strncmp((*token), "on", 2))) {
632 ret = true;
633 } else if ((0 == strncmp((*token), "off", 3))) {
634 ret = false;
635 }
636
637 (*token) = end;
638 return ret;
639}
640
641static inline texture_type_t parseTextureType(
642 const char **token, texture_type_t default_value = TEXTURE_TYPE_NONE) {
643 (*token) += strspn((*token), " \t");
644 const char *end = (*token) + strcspn((*token), " \t\r");
645 texture_type_t ty = default_value;
646
647 if ((0 == strncmp((*token), "cube_top", strlen("cube_top")))) {
648 ty = TEXTURE_TYPE_CUBE_TOP;
649 } else if ((0 == strncmp((*token), "cube_bottom", strlen("cube_bottom")))) {
650 ty = TEXTURE_TYPE_CUBE_BOTTOM;
651 } else if ((0 == strncmp((*token), "cube_left", strlen("cube_left")))) {
652 ty = TEXTURE_TYPE_CUBE_LEFT;
653 } else if ((0 == strncmp((*token), "cube_right", strlen("cube_right")))) {
654 ty = TEXTURE_TYPE_CUBE_RIGHT;
655 } else if ((0 == strncmp((*token), "cube_front", strlen("cube_front")))) {
656 ty = TEXTURE_TYPE_CUBE_FRONT;
657 } else if ((0 == strncmp((*token), "cube_back", strlen("cube_back")))) {
658 ty = TEXTURE_TYPE_CUBE_BACK;
659 } else if ((0 == strncmp((*token), "sphere", strlen("sphere")))) {
660 ty = TEXTURE_TYPE_SPHERE;
661 }
662
663 (*token) = end;
664 return ty;
665}
666
667static tag_sizes parseTagTriple(const char **token) {
668 tag_sizes ts;
669
670 ts.num_ints = atoi((*token));
671 (*token) += strcspn((*token), "/ \t\r");
672 if ((*token)[0] != '/') {
673 return ts;
674 }
675 (*token)++;
676
677 ts.num_reals = atoi((*token));
678 (*token) += strcspn((*token), "/ \t\r");
679 if ((*token)[0] != '/') {
680 return ts;
681 }
682 (*token)++;
683
684 ts.num_strings = atoi((*token));
685 (*token) += strcspn((*token), "/ \t\r") + 1;
686
687 return ts;
688}
689
690// Parse triples with index offsets: i, i/j/k, i//k, i/j
691static vertex_index parseTriple(const char **token, int vsize, int vnsize,
692 int vtsize) {
693 vertex_index vi(-1);
694
695 vi.v_idx = fixIndex(atoi((*token)), vsize);
696 (*token) += strcspn((*token), "/ \t\r");
697 if ((*token)[0] != '/') {
698 return vi;
699 }
700 (*token)++;
701
702 // i//k
703 if ((*token)[0] == '/') {
704 (*token)++;
705 vi.vn_idx = fixIndex(atoi((*token)), vnsize);
706 (*token) += strcspn((*token), "/ \t\r");
707 return vi;
708 }
709
710 // i/j/k or i/j
711 vi.vt_idx = fixIndex(atoi((*token)), vtsize);
712 (*token) += strcspn((*token), "/ \t\r");
713 if ((*token)[0] != '/') {
714 return vi;
715 }
716
717 // i/j/k
718 (*token)++; // skip '/'
719 vi.vn_idx = fixIndex(atoi((*token)), vnsize);
720 (*token) += strcspn((*token), "/ \t\r");
721 return vi;
722}
723
724// Parse raw triples: i, i/j/k, i//k, i/j
725static vertex_index parseRawTriple(const char **token) {
726 vertex_index vi(static_cast<int>(0)); // 0 is an invalid index in OBJ
727
728 vi.v_idx = atoi((*token));
729 (*token) += strcspn((*token), "/ \t\r");
730 if ((*token)[0] != '/') {
731 return vi;
732 }
733 (*token)++;
734
735 // i//k
736 if ((*token)[0] == '/') {
737 (*token)++;
738 vi.vn_idx = atoi((*token));
739 (*token) += strcspn((*token), "/ \t\r");
740 return vi;
741 }
742
743 // i/j/k or i/j
744 vi.vt_idx = atoi((*token));
745 (*token) += strcspn((*token), "/ \t\r");
746 if ((*token)[0] != '/') {
747 return vi;
748 }
749
750 // i/j/k
751 (*token)++; // skip '/'
752 vi.vn_idx = atoi((*token));
753 (*token) += strcspn((*token), "/ \t\r");
754 return vi;
755}
756
757static bool ParseTextureNameAndOption(std::string *texname,
758 texture_option_t *texopt,
759 const char *linebuf, const bool is_bump) {
760 // @todo { write more robust lexer and parser. }
761 bool found_texname = false;
762 std::string texture_name;
763
764 // Fill with default value for texopt.
765 if (is_bump) {
766 texopt->imfchan = 'l';
767 } else {
768 texopt->imfchan = 'm';
769 }
770 texopt->bump_multiplier = 1.0f;
771 texopt->clamp = false;
772 texopt->blendu = true;
773 texopt->blendv = true;
774 texopt->sharpness = 1.0f;
775 texopt->brightness = 0.0f;
776 texopt->contrast = 1.0f;
777 texopt->origin_offset[0] = 0.0f;
778 texopt->origin_offset[1] = 0.0f;
779 texopt->origin_offset[2] = 0.0f;
780 texopt->scale[0] = 1.0f;
781 texopt->scale[1] = 1.0f;
782 texopt->scale[2] = 1.0f;
783 texopt->turbulence[0] = 0.0f;
784 texopt->turbulence[1] = 0.0f;
785 texopt->turbulence[2] = 0.0f;
786 texopt->type = TEXTURE_TYPE_NONE;
787
788 const char *token = linebuf; // Assume line ends with NULL
789
790 while (!IS_NEW_LINE((*token))) {
791 if ((0 == strncmp(token, "-blendu", 7)) && IS_SPACE((token[7]))) {
792 token += 8;
793 texopt->blendu = parseOnOff(&token, /* default */ true);
794 } else if ((0 == strncmp(token, "-blendv", 7)) && IS_SPACE((token[7]))) {
795 token += 8;
796 texopt->blendv = parseOnOff(&token, /* default */ true);
797 } else if ((0 == strncmp(token, "-clamp", 6)) && IS_SPACE((token[6]))) {
798 token += 7;
799 texopt->clamp = parseOnOff(&token, /* default */ true);
800 } else if ((0 == strncmp(token, "-boost", 6)) && IS_SPACE((token[6]))) {
801 token += 7;
802 texopt->sharpness = parseReal(&token, 1.0);
803 } else if ((0 == strncmp(token, "-bm", 3)) && IS_SPACE((token[3]))) {
804 token += 4;
805 texopt->bump_multiplier = parseReal(&token, 1.0);
806 } else if ((0 == strncmp(token, "-o", 2)) && IS_SPACE((token[2]))) {
807 token += 3;
808 parseReal3(&(texopt->origin_offset[0]), &(texopt->origin_offset[1]),
809 &(texopt->origin_offset[2]), &token);
810 } else if ((0 == strncmp(token, "-s", 2)) && IS_SPACE((token[2]))) {
811 token += 3;
812 parseReal3(&(texopt->scale[0]), &(texopt->scale[1]), &(texopt->scale[2]),
813 &token, 1.0, 1.0, 1.0);
814 } else if ((0 == strncmp(token, "-t", 2)) && IS_SPACE((token[2]))) {
815 token += 3;
816 parseReal3(&(texopt->turbulence[0]), &(texopt->turbulence[1]),
817 &(texopt->turbulence[2]), &token);
818 } else if ((0 == strncmp(token, "-type", 5)) && IS_SPACE((token[5]))) {
819 token += 5;
820 texopt->type = parseTextureType((&token), TEXTURE_TYPE_NONE);
821 } else if ((0 == strncmp(token, "-imfchan", 8)) && IS_SPACE((token[8]))) {
822 token += 9;
823 token += strspn(token, " \t");
824 const char *end = token + strcspn(token, " \t\r");
825 if ((end - token) == 1) { // Assume one char for -imfchan
826 texopt->imfchan = (*token);
827 }
828 token = end;
829 } else if ((0 == strncmp(token, "-mm", 3)) && IS_SPACE((token[3]))) {
830 token += 4;
831 parseReal2(&(texopt->brightness), &(texopt->contrast), &token, 0.0, 1.0);
832 } else {
833 // Assume texture filename
834 token += strspn(token, " \t"); // skip space
835 size_t len = strcspn(token, " \t\r"); // untile next space
836 texture_name = std::string(token, token + len);
837 token += len;
838
839 token += strspn(token, " \t"); // skip space
840
841 found_texname = true;
842 }
843 }
844
845 if (found_texname) {
846 (*texname) = texture_name;
847 return true;
848 } else {
849 return false;
850 }
851}
852
853static void InitMaterial(material_t *material) {
854 material->name = "";
855 material->ambient_texname = "";
856 material->diffuse_texname = "";
857 material->specular_texname = "";
858 material->specular_highlight_texname = "";
859 material->bump_texname = "";
860 material->displacement_texname = "";
861 material->alpha_texname = "";
862 for (int i = 0; i < 3; i++) {
863 material->ambient[i] = 0.f;
864 material->diffuse[i] = 0.f;
865 material->specular[i] = 0.f;
866 material->transmittance[i] = 0.f;
867 material->emission[i] = 0.f;
868 }
869 material->illum = 0;
870 material->dissolve = 1.f;
871 material->shininess = 1.f;
872 material->ior = 1.f;
873
874 material->roughness = 0.f;
875 material->metallic = 0.f;
876 material->sheen = 0.f;
877 material->clearcoat_thickness = 0.f;
878 material->clearcoat_roughness = 0.f;
879 material->anisotropy_rotation = 0.f;
880 material->anisotropy = 0.f;
881 material->roughness_texname = "";
882 material->metallic_texname = "";
883 material->sheen_texname = "";
884 material->emissive_texname = "";
885 material->normal_texname = "";
886
887 material->unknown_parameter.clear();
888}
889
890static bool exportFaceGroupToShape(
891 shape_t *shape, const std::vector<std::vector<vertex_index> > &faceGroup,
892 const std::vector<tag_t> &tags, const int material_id,
893 const std::string &name, bool triangulate) {
894 if (faceGroup.empty()) {
895 return false;
896 }
897
898 // Flatten vertices and indices
899 for (size_t i = 0; i < faceGroup.size(); i++) {
900 const std::vector<vertex_index> &face = faceGroup[i];
901
902 vertex_index i0 = face[0];
903 vertex_index i1(-1);
904 vertex_index i2 = face[1];
905
906 size_t npolys = face.size();
907
908 if (triangulate) {
909 // Polygon -> triangle fan conversion
910 for (size_t k = 2; k < npolys; k++) {
911 i1 = i2;
912 i2 = face[k];
913
914 index_t idx0, idx1, idx2;
915 idx0.vertex_index = i0.v_idx;
916 idx0.normal_index = i0.vn_idx;
917 idx0.texcoord_index = i0.vt_idx;
918 idx1.vertex_index = i1.v_idx;
919 idx1.normal_index = i1.vn_idx;
920 idx1.texcoord_index = i1.vt_idx;
921 idx2.vertex_index = i2.v_idx;
922 idx2.normal_index = i2.vn_idx;
923 idx2.texcoord_index = i2.vt_idx;
924
925 shape->mesh.indices.push_back(idx0);
926 shape->mesh.indices.push_back(idx1);
927 shape->mesh.indices.push_back(idx2);
928
929 shape->mesh.num_face_vertices.push_back(3);
930 shape->mesh.material_ids.push_back(material_id);
931 }
932 } else {
933 for (size_t k = 0; k < npolys; k++) {
934 index_t idx;
935 idx.vertex_index = face[k].v_idx;
936 idx.normal_index = face[k].vn_idx;
937 idx.texcoord_index = face[k].vt_idx;
938 shape->mesh.indices.push_back(idx);
939 }
940
941 shape->mesh.num_face_vertices.push_back(
942 static_cast<unsigned char>(npolys));
943 shape->mesh.material_ids.push_back(material_id); // per face
944 }
945 }
946
947 shape->name = name;
948 shape->mesh.tags = tags;
949
950 return true;
951}
952
953// Split a string with specified delimiter character.
954// http://stackoverflow.com/questions/236129/split-a-string-in-c
955static void SplitString(const std::string &s, char delim,
956 std::vector<std::string> &elems) {
957 std::stringstream ss;
958 ss.str(s);
959 std::string item;
960 while (std::getline(ss, item, delim)) {
961 elems.push_back(item);
962 }
963}
964
965void LoadMtl(std::map<std::string, int> *material_map,
966 std::vector<material_t> *materials, std::istream *inStream,
967 std::string *warning) {
968 // Create a default material anyway.
969 material_t material;
970 InitMaterial(&material);
971
972 // Issue 43. `d` wins against `Tr` since `Tr` is not in the MTL specification.
973 bool has_d = false;
974 bool has_tr = false;
975
976 std::stringstream ss;
977
978 std::string linebuf;
979 while (inStream->peek() != -1) {
980 safeGetline(*inStream, linebuf);
981
982 // Trim trailing whitespace.
983 if (linebuf.size() > 0) {
984 linebuf = linebuf.substr(0, linebuf.find_last_not_of(" \t") + 1);
985 }
986
987 // Trim newline '\r\n' or '\n'
988 if (linebuf.size() > 0) {
989 if (linebuf[linebuf.size() - 1] == '\n')
990 linebuf.erase(linebuf.size() - 1);
991 }
992 if (linebuf.size() > 0) {
993 if (linebuf[linebuf.size() - 1] == '\r')
994 linebuf.erase(linebuf.size() - 1);
995 }
996
997 // Skip if empty line.
998 if (linebuf.empty()) {
999 continue;
1000 }
1001
1002 // Skip leading space.
1003 const char *token = linebuf.c_str();
1004 token += strspn(token, " \t");
1005
1006 assert(token);
1007 if (token[0] == '\0') continue; // empty line
1008
1009 if (token[0] == '#') continue; // comment line
1010
1011 // new mtl
1012 if ((0 == strncmp(token, "newmtl", 6)) && IS_SPACE((token[6]))) {
1013 // flush previous material.
1014 if (!material.name.empty()) {
1015 material_map->insert(std::pair<std::string, int>(
1016 material.name, static_cast<int>(materials->size())));
1017 materials->push_back(material);
1018 }
1019
1020 // initial temporary material
1021 InitMaterial(&material);
1022
1023 has_d = false;
1024 has_tr = false;
1025
1026 // set new mtl name
1027 char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE];
1028 token += 7;
1029#ifdef _MSC_VER
1030 sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf));
1031#else
1032 std::sscanf(token, "%s", namebuf);
1033#endif
1034 material.name = namebuf;
1035 continue;
1036 }
1037
1038 // ambient
1039 if (token[0] == 'K' && token[1] == 'a' && IS_SPACE((token[2]))) {
1040 token += 2;
1041 real_t r, g, b;
1042 parseReal3(&r, &g, &b, &token);
1043 material.ambient[0] = r;
1044 material.ambient[1] = g;
1045 material.ambient[2] = b;
1046 continue;
1047 }
1048
1049 // diffuse
1050 if (token[0] == 'K' && token[1] == 'd' && IS_SPACE((token[2]))) {
1051 token += 2;
1052 real_t r, g, b;
1053 parseReal3(&r, &g, &b, &token);
1054 material.diffuse[0] = r;
1055 material.diffuse[1] = g;
1056 material.diffuse[2] = b;
1057 continue;
1058 }
1059
1060 // specular
1061 if (token[0] == 'K' && token[1] == 's' && IS_SPACE((token[2]))) {
1062 token += 2;
1063 real_t r, g, b;
1064 parseReal3(&r, &g, &b, &token);
1065 material.specular[0] = r;
1066 material.specular[1] = g;
1067 material.specular[2] = b;
1068 continue;
1069 }
1070
1071 // transmittance
1072 if ((token[0] == 'K' && token[1] == 't' && IS_SPACE((token[2]))) ||
1073 (token[0] == 'T' && token[1] == 'f' && IS_SPACE((token[2])))) {
1074 token += 2;
1075 real_t r, g, b;
1076 parseReal3(&r, &g, &b, &token);
1077 material.transmittance[0] = r;
1078 material.transmittance[1] = g;
1079 material.transmittance[2] = b;
1080 continue;
1081 }
1082
1083 // ior(index of refraction)
1084 if (token[0] == 'N' && token[1] == 'i' && IS_SPACE((token[2]))) {
1085 token += 2;
1086 material.ior = parseReal(&token);
1087 continue;
1088 }
1089
1090 // emission
1091 if (token[0] == 'K' && token[1] == 'e' && IS_SPACE(token[2])) {
1092 token += 2;
1093 real_t r, g, b;
1094 parseReal3(&r, &g, &b, &token);
1095 material.emission[0] = r;
1096 material.emission[1] = g;
1097 material.emission[2] = b;
1098 continue;
1099 }
1100
1101 // shininess
1102 if (token[0] == 'N' && token[1] == 's' && IS_SPACE(token[2])) {
1103 token += 2;
1104 material.shininess = parseReal(&token);
1105 continue;
1106 }
1107
1108 // illum model
1109 if (0 == strncmp(token, "illum", 5) && IS_SPACE(token[5])) {
1110 token += 6;
1111 material.illum = parseInt(&token);
1112 continue;
1113 }
1114
1115 // dissolve
1116 if ((token[0] == 'd' && IS_SPACE(token[1]))) {
1117 token += 1;
1118 material.dissolve = parseReal(&token);
1119
1120 if (has_tr) {
1121 ss << "WARN: Both `d` and `Tr` parameters defined for \""
1122 << material.name << "\". Use the value of `d` for dissolve."
1123 << std::endl;
1124 }
1125 has_d = true;
1126 continue;
1127 }
1128 if (token[0] == 'T' && token[1] == 'r' && IS_SPACE(token[2])) {
1129 token += 2;
1130 if (has_d) {
1131 // `d` wins. Ignore `Tr` value.
1132 ss << "WARN: Both `d` and `Tr` parameters defined for \""
1133 << material.name << "\". Use the value of `d` for dissolve."
1134 << std::endl;
1135 } else {
1136 // We invert value of Tr(assume Tr is in range [0, 1])
1137 // NOTE: Interpretation of Tr is application(exporter) dependent. For
1138 // some application(e.g. 3ds max obj exporter), Tr = d(Issue 43)
1139 material.dissolve = 1.0f - parseReal(&token);
1140 }
1141 has_tr = true;
1142 continue;
1143 }
1144
1145 // PBR: roughness
1146 if (token[0] == 'P' && token[1] == 'r' && IS_SPACE(token[2])) {
1147 token += 2;
1148 material.roughness = parseReal(&token);
1149 continue;
1150 }
1151
1152 // PBR: metallic
1153 if (token[0] == 'P' && token[1] == 'm' && IS_SPACE(token[2])) {
1154 token += 2;
1155 material.metallic = parseReal(&token);
1156 continue;
1157 }
1158
1159 // PBR: sheen
1160 if (token[0] == 'P' && token[1] == 's' && IS_SPACE(token[2])) {
1161 token += 2;
1162 material.sheen = parseReal(&token);
1163 continue;
1164 }
1165
1166 // PBR: clearcoat thickness
1167 if (token[0] == 'P' && token[1] == 'c' && IS_SPACE(token[2])) {
1168 token += 2;
1169 material.clearcoat_thickness = parseReal(&token);
1170 continue;
1171 }
1172
1173 // PBR: clearcoat roughness
1174 if ((0 == strncmp(token, "Pcr", 3)) && IS_SPACE(token[3])) {
1175 token += 4;
1176 material.clearcoat_roughness = parseReal(&token);
1177 continue;
1178 }
1179
1180 // PBR: anisotropy
1181 if ((0 == strncmp(token, "aniso", 5)) && IS_SPACE(token[5])) {
1182 token += 6;
1183 material.anisotropy = parseReal(&token);
1184 continue;
1185 }
1186
1187 // PBR: anisotropy rotation
1188 if ((0 == strncmp(token, "anisor", 6)) && IS_SPACE(token[6])) {
1189 token += 7;
1190 material.anisotropy_rotation = parseReal(&token);
1191 continue;
1192 }
1193
1194 // ambient texture
1195 if ((0 == strncmp(token, "map_Ka", 6)) && IS_SPACE(token[6])) {
1196 token += 7;
1197 ParseTextureNameAndOption(&(material.ambient_texname),
1198 &(material.ambient_texopt), token,
1199 /* is_bump */ false);
1200 continue;
1201 }
1202
1203 // diffuse texture
1204 if ((0 == strncmp(token, "map_Kd", 6)) && IS_SPACE(token[6])) {
1205 token += 7;
1206 ParseTextureNameAndOption(&(material.diffuse_texname),
1207 &(material.diffuse_texopt), token,
1208 /* is_bump */ false);
1209 continue;
1210 }
1211
1212 // specular texture
1213 if ((0 == strncmp(token, "map_Ks", 6)) && IS_SPACE(token[6])) {
1214 token += 7;
1215 ParseTextureNameAndOption(&(material.specular_texname),
1216 &(material.specular_texopt), token,
1217 /* is_bump */ false);
1218 continue;
1219 }
1220
1221 // specular highlight texture
1222 if ((0 == strncmp(token, "map_Ns", 6)) && IS_SPACE(token[6])) {
1223 token += 7;
1224 ParseTextureNameAndOption(&(material.specular_highlight_texname),
1225 &(material.specular_highlight_texopt), token,
1226 /* is_bump */ false);
1227 continue;
1228 }
1229
1230 // bump texture
1231 if ((0 == strncmp(token, "map_bump", 8)) && IS_SPACE(token[8])) {
1232 token += 9;
1233 ParseTextureNameAndOption(&(material.bump_texname),
1234 &(material.bump_texopt), token,
1235 /* is_bump */ true);
1236 continue;
1237 }
1238
1239 // bump texture
1240 if ((0 == strncmp(token, "bump", 4)) && IS_SPACE(token[4])) {
1241 token += 5;
1242 ParseTextureNameAndOption(&(material.bump_texname),
1243 &(material.bump_texopt), token,
1244 /* is_bump */ true);
1245 continue;
1246 }
1247
1248 // alpha texture
1249 if ((0 == strncmp(token, "map_d", 5)) && IS_SPACE(token[5])) {
1250 token += 6;
1251 material.alpha_texname = token;
1252 ParseTextureNameAndOption(&(material.alpha_texname),
1253 &(material.alpha_texopt), token,
1254 /* is_bump */ false);
1255 continue;
1256 }
1257
1258 // displacement texture
1259 if ((0 == strncmp(token, "disp", 4)) && IS_SPACE(token[4])) {
1260 token += 5;
1261 ParseTextureNameAndOption(&(material.displacement_texname),
1262 &(material.displacement_texopt), token,
1263 /* is_bump */ false);
1264 continue;
1265 }
1266
1267 // PBR: roughness texture
1268 if ((0 == strncmp(token, "map_Pr", 6)) && IS_SPACE(token[6])) {
1269 token += 7;
1270 ParseTextureNameAndOption(&(material.roughness_texname),
1271 &(material.roughness_texopt), token,
1272 /* is_bump */ false);
1273 continue;
1274 }
1275
1276 // PBR: metallic texture
1277 if ((0 == strncmp(token, "map_Pm", 6)) && IS_SPACE(token[6])) {
1278 token += 7;
1279 ParseTextureNameAndOption(&(material.metallic_texname),
1280 &(material.metallic_texopt), token,
1281 /* is_bump */ false);
1282 continue;
1283 }
1284
1285 // PBR: sheen texture
1286 if ((0 == strncmp(token, "map_Ps", 6)) && IS_SPACE(token[6])) {
1287 token += 7;
1288 ParseTextureNameAndOption(&(material.sheen_texname),
1289 &(material.sheen_texopt), token,
1290 /* is_bump */ false);
1291 continue;
1292 }
1293
1294 // PBR: emissive texture
1295 if ((0 == strncmp(token, "map_Ke", 6)) && IS_SPACE(token[6])) {
1296 token += 7;
1297 ParseTextureNameAndOption(&(material.emissive_texname),
1298 &(material.emissive_texopt), token,
1299 /* is_bump */ false);
1300 continue;
1301 }
1302
1303 // PBR: normal map texture
1304 if ((0 == strncmp(token, "norm", 4)) && IS_SPACE(token[4])) {
1305 token += 5;
1306 ParseTextureNameAndOption(
1307 &(material.normal_texname), &(material.normal_texopt), token,
1308 /* is_bump */ false); // @fixme { is_bump will be true? }
1309 continue;
1310 }
1311
1312 // unknown parameter
1313 const char *_space = strchr(token, ' ');
1314 if (!_space) {
1315 _space = strchr(token, '\t');
1316 }
1317 if (_space) {
1318 std::ptrdiff_t len = _space - token;
1319 std::string key(token, static_cast<size_t>(len));
1320 std::string value = _space + 1;
1321 material.unknown_parameter.insert(
1322 std::pair<std::string, std::string>(key, value));
1323 }
1324 }
1325 // flush last material.
1326 material_map->insert(std::pair<std::string, int>(
1327 material.name, static_cast<int>(materials->size())));
1328 materials->push_back(material);
1329
1330 if (warning) {
1331 (*warning) = ss.str();
1332 }
1333}
1334
1335bool MaterialFileReader::operator()(const std::string &matId,
1336 std::vector<material_t> *materials,
1337 std::map<std::string, int> *matMap,
1338 std::string *err) {
1339 std::string filepath;
1340
1341 if (!m_mtlBaseDir.empty()) {
1342 filepath = std::string(m_mtlBaseDir) + matId;
1343 } else {
1344 filepath = matId;
1345 }
1346
1347 std::ifstream matIStream(filepath.c_str());
1348 if (!matIStream) {
1349 std::stringstream ss;
1350 ss << "WARN: Material file [ " << filepath << " ] not found." << std::endl;
1351 if (err) {
1352 (*err) += ss.str();
1353 }
1354 return false;
1355 }
1356
1357 std::string warning;
1358 LoadMtl(matMap, materials, &matIStream, &warning);
1359
1360 if (!warning.empty()) {
1361 if (err) {
1362 (*err) += warning;
1363 }
1364 }
1365
1366 return true;
1367}
1368
1369bool MaterialStreamReader::operator()(const std::string &matId,
1370 std::vector<material_t> *materials,
1371 std::map<std::string, int> *matMap,
1372 std::string *err) {
1373 (void)matId;
1374 if (!m_inStream) {
1375 std::stringstream ss;
1376 ss << "WARN: Material stream in error state. " << std::endl;
1377 if (err) {
1378 (*err) += ss.str();
1379 }
1380 return false;
1381 }
1382
1383 std::string warning;
1384 LoadMtl(matMap, materials, &m_inStream, &warning);
1385
1386 if (!warning.empty()) {
1387 if (err) {
1388 (*err) += warning;
1389 }
1390 }
1391
1392 return true;
1393}
1394
1395bool LoadObj(attrib_t *attrib, std::vector<shape_t> *shapes,
1396 std::vector<material_t> *materials, std::string *err,
1397 const char *filename, const char *mtl_basedir, bool trianglulate) {
1398 attrib->vertices.clear();
1399 attrib->normals.clear();
1400 attrib->texcoords.clear();
1401 shapes->clear();
1402
1403 std::stringstream errss;
1404
1405 std::ifstream ifs(filename);
1406 if (!ifs) {
1407 errss << "Cannot open file [" << filename << "]" << std::endl;
1408 if (err) {
1409 (*err) = errss.str();
1410 }
1411 return false;
1412 }
1413
1414 std::string baseDir;
1415 if (mtl_basedir) {
1416 baseDir = mtl_basedir;
1417 }
1418 MaterialFileReader matFileReader(baseDir);
1419
1420 return LoadObj(attrib, shapes, materials, err, &ifs, &matFileReader,
1421 trianglulate);
1422}
1423
1424bool LoadObj(attrib_t *attrib, std::vector<shape_t> *shapes,
1425 std::vector<material_t> *materials, std::string *err,
1426 std::istream *inStream, MaterialReader *readMatFn /*= NULL*/,
1427 bool triangulate) {
1428 std::stringstream errss;
1429
1430 std::vector<real_t> v;
1431 std::vector<real_t> vn;
1432 std::vector<real_t> vt;
1433 std::vector<tag_t> tags;
1434 std::vector<std::vector<vertex_index> > faceGroup;
1435 std::string name;
1436
1437 // material
1438 std::map<std::string, int> material_map;
1439 int material = -1;
1440
1441 shape_t shape;
1442
1443 std::string linebuf;
1444 while (inStream->peek() != -1) {
1445 safeGetline(*inStream, linebuf);
1446
1447 // Trim newline '\r\n' or '\n'
1448 if (linebuf.size() > 0) {
1449 if (linebuf[linebuf.size() - 1] == '\n')
1450 linebuf.erase(linebuf.size() - 1);
1451 }
1452 if (linebuf.size() > 0) {
1453 if (linebuf[linebuf.size() - 1] == '\r')
1454 linebuf.erase(linebuf.size() - 1);
1455 }
1456
1457 // Skip if empty line.
1458 if (linebuf.empty()) {
1459 continue;
1460 }
1461
1462 // Skip leading space.
1463 const char *token = linebuf.c_str();
1464 token += strspn(token, " \t");
1465
1466 assert(token);
1467 if (token[0] == '\0') continue; // empty line
1468
1469 if (token[0] == '#') continue; // comment line
1470
1471 // vertex
1472 if (token[0] == 'v' && IS_SPACE((token[1]))) {
1473 token += 2;
1474 real_t x, y, z;
1475 parseReal3(&x, &y, &z, &token);
1476 v.push_back(x);
1477 v.push_back(y);
1478 v.push_back(z);
1479 continue;
1480 }
1481
1482 // normal
1483 if (token[0] == 'v' && token[1] == 'n' && IS_SPACE((token[2]))) {
1484 token += 3;
1485 real_t x, y, z;
1486 parseReal3(&x, &y, &z, &token);
1487 vn.push_back(x);
1488 vn.push_back(y);
1489 vn.push_back(z);
1490 continue;
1491 }
1492
1493 // texcoord
1494 if (token[0] == 'v' && token[1] == 't' && IS_SPACE((token[2]))) {
1495 token += 3;
1496 real_t x, y;
1497 parseReal2(&x, &y, &token);
1498 vt.push_back(x);
1499 vt.push_back(y);
1500 continue;
1501 }
1502
1503 // face
1504 if (token[0] == 'f' && IS_SPACE((token[1]))) {
1505 token += 2;
1506 token += strspn(token, " \t");
1507
1508 std::vector<vertex_index> face;
1509 face.reserve(3);
1510
1511 while (!IS_NEW_LINE(token[0])) {
1512 vertex_index vi = parseTriple(&token, static_cast<int>(v.size() / 3),
1513 static_cast<int>(vn.size() / 3),
1514 static_cast<int>(vt.size() / 2));
1515 face.push_back(vi);
1516 size_t n = strspn(token, " \t\r");
1517 token += n;
1518 }
1519
1520 // replace with emplace_back + std::move on C++11
1521 faceGroup.push_back(std::vector<vertex_index>());
1522 faceGroup[faceGroup.size() - 1].swap(face);
1523
1524 continue;
1525 }
1526
1527 // use mtl
1528 if ((0 == strncmp(token, "usemtl", 6)) && IS_SPACE((token[6]))) {
1529 char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE];
1530 token += 7;
1531#ifdef _MSC_VER
1532 sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf));
1533#else
1534 std::sscanf(token, "%s", namebuf);
1535#endif
1536
1537 int newMaterialId = -1;
1538 if (material_map.find(namebuf) != material_map.end()) {
1539 newMaterialId = material_map[namebuf];
1540 } else {
1541 // { error!! material not found }
1542 }
1543
1544 if (newMaterialId != material) {
1545 // Create per-face material. Thus we don't add `shape` to `shapes` at
1546 // this time.
1547 // just clear `faceGroup` after `exportFaceGroupToShape()` call.
1548 exportFaceGroupToShape(&shape, faceGroup, tags, material, name,
1549 triangulate);
1550 faceGroup.clear();
1551 material = newMaterialId;
1552 }
1553
1554 continue;
1555 }
1556
1557 // load mtl
1558 if ((0 == strncmp(token, "mtllib", 6)) && IS_SPACE((token[6]))) {
1559 if (readMatFn) {
1560 token += 7;
1561
1562 std::vector<std::string> filenames;
1563 SplitString(std::string(token), ' ', filenames);
1564
1565 if (filenames.empty()) {
1566 if (err) {
1567 (*err) +=
1568 "WARN: Looks like empty filename for mtllib. Use default "
1569 "material. \n";
1570 }
1571 } else {
1572 bool found = false;
1573 for (size_t s = 0; s < filenames.size(); s++) {
1574 std::string err_mtl;
1575 bool ok = (*readMatFn)(filenames[s].c_str(), materials,
1576 &material_map, &err_mtl);
1577 if (err && (!err_mtl.empty())) {
1578 (*err) += err_mtl; // This should be warn message.
1579 }
1580
1581 if (ok) {
1582 found = true;
1583 break;
1584 }
1585 }
1586
1587 if (!found) {
1588 if (err) {
1589 (*err) +=
1590 "WARN: Failed to load material file(s). Use default "
1591 "material.\n";
1592 }
1593 }
1594 }
1595 }
1596
1597 continue;
1598 }
1599
1600 // group name
1601 if (token[0] == 'g' && IS_SPACE((token[1]))) {
1602 // flush previous face group.
1603 bool ret = exportFaceGroupToShape(&shape, faceGroup, tags, material, name,
1604 triangulate);
1605 if (ret) {
1606 shapes->push_back(shape);
1607 }
1608
1609 shape = shape_t();
1610
1611 // material = -1;
1612 faceGroup.clear();
1613
1614 std::vector<std::string> names;
1615 names.reserve(2);
1616
1617 while (!IS_NEW_LINE(token[0])) {
1618 std::string str = parseString(&token);
1619 names.push_back(str);
1620 token += strspn(token, " \t\r"); // skip tag
1621 }
1622
1623 assert(names.size() > 0);
1624
1625 // names[0] must be 'g', so skip the 0th element.
1626 if (names.size() > 1) {
1627 name = names[1];
1628 } else {
1629 name = "";
1630 }
1631
1632 continue;
1633 }
1634
1635 // object name
1636 if (token[0] == 'o' && IS_SPACE((token[1]))) {
1637 // flush previous face group.
1638 bool ret = exportFaceGroupToShape(&shape, faceGroup, tags, material, name,
1639 triangulate);
1640 if (ret) {
1641 shapes->push_back(shape);
1642 }
1643
1644 // material = -1;
1645 faceGroup.clear();
1646 shape = shape_t();
1647
1648 // @todo { multiple object name? }
1649 char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE];
1650 token += 2;
1651#ifdef _MSC_VER
1652 sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf));
1653#else
1654 std::sscanf(token, "%s", namebuf);
1655#endif
1656 name = std::string(namebuf);
1657
1658 continue;
1659 }
1660
1661 if (token[0] == 't' && IS_SPACE(token[1])) {
1662 tag_t tag;
1663
1664 char namebuf[4096];
1665 token += 2;
1666#ifdef _MSC_VER
1667 sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf));
1668#else
1669 std::sscanf(token, "%s", namebuf);
1670#endif
1671 tag.name = std::string(namebuf);
1672
1673 token += tag.name.size() + 1;
1674
1675 tag_sizes ts = parseTagTriple(&token);
1676
1677 tag.intValues.resize(static_cast<size_t>(ts.num_ints));
1678
1679 for (size_t i = 0; i < static_cast<size_t>(ts.num_ints); ++i) {
1680 tag.intValues[i] = atoi(token);
1681 token += strcspn(token, "/ \t\r") + 1;
1682 }
1683
1684 tag.floatValues.resize(static_cast<size_t>(ts.num_reals));
1685 for (size_t i = 0; i < static_cast<size_t>(ts.num_reals); ++i) {
1686 tag.floatValues[i] = parseReal(&token);
1687 token += strcspn(token, "/ \t\r") + 1;
1688 }
1689
1690 tag.stringValues.resize(static_cast<size_t>(ts.num_strings));
1691 for (size_t i = 0; i < static_cast<size_t>(ts.num_strings); ++i) {
1692 char stringValueBuffer[4096];
1693
1694#ifdef _MSC_VER
1695 sscanf_s(token, "%s", stringValueBuffer,
1696 (unsigned)_countof(stringValueBuffer));
1697#else
1698 std::sscanf(token, "%s", stringValueBuffer);
1699#endif
1700 tag.stringValues[i] = stringValueBuffer;
1701 token += tag.stringValues[i].size() + 1;
1702 }
1703
1704 tags.push_back(tag);
1705 }
1706
1707 // Ignore unknown command.
1708 }
1709
1710 bool ret = exportFaceGroupToShape(&shape, faceGroup, tags, material, name,
1711 triangulate);
1712 // exportFaceGroupToShape return false when `usemtl` is called in the last
1713 // line.
1714 // we also add `shape` to `shapes` when `shape.mesh` has already some
1715 // faces(indices)
1716 if (ret || shape.mesh.indices.size()) {
1717 shapes->push_back(shape);
1718 }
1719 faceGroup.clear(); // for safety
1720
1721 if (err) {
1722 (*err) += errss.str();
1723 }
1724
1725 attrib->vertices.swap(v);
1726 attrib->normals.swap(vn);
1727 attrib->texcoords.swap(vt);
1728
1729 return true;
1730}
1731
1732bool LoadObjWithCallback(std::istream &inStream, const callback_t &callback,
1733 void *user_data /*= NULL*/,
1734 MaterialReader *readMatFn /*= NULL*/,
1735 std::string *err /*= NULL*/) {
1736 std::stringstream errss;
1737
1738 // material
1739 std::map<std::string, int> material_map;
1740 int material_id = -1; // -1 = invalid
1741
1742 std::vector<index_t> indices;
1743 std::vector<material_t> materials;
1744 std::vector<std::string> names;
1745 names.reserve(2);
1746 std::string name;
1747 std::vector<const char *> names_out;
1748
1749 std::string linebuf;
1750 while (inStream.peek() != -1) {
1751 safeGetline(inStream, linebuf);
1752
1753 // Trim newline '\r\n' or '\n'
1754 if (linebuf.size() > 0) {
1755 if (linebuf[linebuf.size() - 1] == '\n')
1756 linebuf.erase(linebuf.size() - 1);
1757 }
1758 if (linebuf.size() > 0) {
1759 if (linebuf[linebuf.size() - 1] == '\r')
1760 linebuf.erase(linebuf.size() - 1);
1761 }
1762
1763 // Skip if empty line.
1764 if (linebuf.empty()) {
1765 continue;
1766 }
1767
1768 // Skip leading space.
1769 const char *token = linebuf.c_str();
1770 token += strspn(token, " \t");
1771
1772 assert(token);
1773 if (token[0] == '\0') continue; // empty line
1774
1775 if (token[0] == '#') continue; // comment line
1776
1777 // vertex
1778 if (token[0] == 'v' && IS_SPACE((token[1]))) {
1779 token += 2;
1780 real_t x, y, z, w; // w is optional. default = 1.0
1781 parseV(&x, &y, &z, &w, &token);
1782 if (callback.vertex_cb) {
1783 callback.vertex_cb(user_data, x, y, z, w);
1784 }
1785 continue;
1786 }
1787
1788 // normal
1789 if (token[0] == 'v' && token[1] == 'n' && IS_SPACE((token[2]))) {
1790 token += 3;
1791 real_t x, y, z;
1792 parseReal3(&x, &y, &z, &token);
1793 if (callback.normal_cb) {
1794 callback.normal_cb(user_data, x, y, z);
1795 }
1796 continue;
1797 }
1798
1799 // texcoord
1800 if (token[0] == 'v' && token[1] == 't' && IS_SPACE((token[2]))) {
1801 token += 3;
1802 real_t x, y, z; // y and z are optional. default = 0.0
1803 parseReal3(&x, &y, &z, &token);
1804 if (callback.texcoord_cb) {
1805 callback.texcoord_cb(user_data, x, y, z);
1806 }
1807 continue;
1808 }
1809
1810 // face
1811 if (token[0] == 'f' && IS_SPACE((token[1]))) {
1812 token += 2;
1813 token += strspn(token, " \t");
1814
1815 indices.clear();
1816 while (!IS_NEW_LINE(token[0])) {
1817 vertex_index vi = parseRawTriple(&token);
1818
1819 index_t idx;
1820 idx.vertex_index = vi.v_idx;
1821 idx.normal_index = vi.vn_idx;
1822 idx.texcoord_index = vi.vt_idx;
1823
1824 indices.push_back(idx);
1825 size_t n = strspn(token, " \t\r");
1826 token += n;
1827 }
1828
1829 if (callback.index_cb && indices.size() > 0) {
1830 callback.index_cb(user_data, &indices.at(0),
1831 static_cast<int>(indices.size()));
1832 }
1833
1834 continue;
1835 }
1836
1837 // use mtl
1838 if ((0 == strncmp(token, "usemtl", 6)) && IS_SPACE((token[6]))) {
1839 char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE];
1840 token += 7;
1841#ifdef _MSC_VER
1842 sscanf_s(token, "%s", namebuf,
1843 static_cast<unsigned int>(_countof(namebuf)));
1844#else
1845 std::sscanf(token, "%s", namebuf);
1846#endif
1847
1848 int newMaterialId = -1;
1849 if (material_map.find(namebuf) != material_map.end()) {
1850 newMaterialId = material_map[namebuf];
1851 } else {
1852 // { error!! material not found }
1853 }
1854
1855 if (newMaterialId != material_id) {
1856 material_id = newMaterialId;
1857 }
1858
1859 if (callback.usemtl_cb) {
1860 callback.usemtl_cb(user_data, namebuf, material_id);
1861 }
1862
1863 continue;
1864 }
1865
1866 // load mtl
1867 if ((0 == strncmp(token, "mtllib", 6)) && IS_SPACE((token[6]))) {
1868 if (readMatFn) {
1869 token += 7;
1870
1871 std::vector<std::string> filenames;
1872 SplitString(std::string(token), ' ', filenames);
1873
1874 if (filenames.empty()) {
1875 if (err) {
1876 (*err) +=
1877 "WARN: Looks like empty filename for mtllib. Use default "
1878 "material. \n";
1879 }
1880 } else {
1881 bool found = false;
1882 for (size_t s = 0; s < filenames.size(); s++) {
1883 std::string err_mtl;
1884 bool ok = (*readMatFn)(filenames[s].c_str(), &materials,
1885 &material_map, &err_mtl);
1886 if (err && (!err_mtl.empty())) {
1887 (*err) += err_mtl; // This should be warn message.
1888 }
1889
1890 if (ok) {
1891 found = true;
1892 break;
1893 }
1894 }
1895
1896 if (!found) {
1897 if (err) {
1898 (*err) +=
1899 "WARN: Failed to load material file(s). Use default "
1900 "material.\n";
1901 }
1902 } else {
1903 if (callback.mtllib_cb) {
1904 callback.mtllib_cb(user_data, &materials.at(0),
1905 static_cast<int>(materials.size()));
1906 }
1907 }
1908 }
1909 }
1910
1911 continue;
1912 }
1913
1914 // group name
1915 if (token[0] == 'g' && IS_SPACE((token[1]))) {
1916 names.clear();
1917
1918 while (!IS_NEW_LINE(token[0])) {
1919 std::string str = parseString(&token);
1920 names.push_back(str);
1921 token += strspn(token, " \t\r"); // skip tag
1922 }
1923
1924 assert(names.size() > 0);
1925
1926 // names[0] must be 'g', so skip the 0th element.
1927 if (names.size() > 1) {
1928 name = names[1];
1929 } else {
1930 name.clear();
1931 }
1932
1933 if (callback.group_cb) {
1934 if (names.size() > 1) {
1935 // create const char* array.
1936 names_out.resize(names.size() - 1);
1937 for (size_t j = 0; j < names_out.size(); j++) {
1938 names_out[j] = names[j + 1].c_str();
1939 }
1940 callback.group_cb(user_data, &names_out.at(0),
1941 static_cast<int>(names_out.size()));
1942
1943 } else {
1944 callback.group_cb(user_data, NULL, 0);
1945 }
1946 }
1947
1948 continue;
1949 }
1950
1951 // object name
1952 if (token[0] == 'o' && IS_SPACE((token[1]))) {
1953 // @todo { multiple object name? }
1954 char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE];
1955 token += 2;
1956#ifdef _MSC_VER
1957 sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf));
1958#else
1959 std::sscanf(token, "%s", namebuf);
1960#endif
1961 std::string object_name = std::string(namebuf);
1962
1963 if (callback.object_cb) {
1964 callback.object_cb(user_data, object_name.c_str());
1965 }
1966
1967 continue;
1968 }
1969
1970#if 0 // @todo
1971 if (token[0] == 't' && IS_SPACE(token[1])) {
1972 tag_t tag;
1973
1974 char namebuf[4096];
1975 token += 2;
1976#ifdef _MSC_VER
1977 sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf));
1978#else
1979 std::sscanf(token, "%s", namebuf);
1980#endif
1981 tag.name = std::string(namebuf);
1982
1983 token += tag.name.size() + 1;
1984
1985 tag_sizes ts = parseTagTriple(&token);
1986
1987 tag.intValues.resize(static_cast<size_t>(ts.num_ints));
1988
1989 for (size_t i = 0; i < static_cast<size_t>(ts.num_ints); ++i) {
1990 tag.intValues[i] = atoi(token);
1991 token += strcspn(token, "/ \t\r") + 1;
1992 }
1993
1994 tag.floatValues.resize(static_cast<size_t>(ts.num_reals));
1995 for (size_t i = 0; i < static_cast<size_t>(ts.num_reals); ++i) {
1996 tag.floatValues[i] = parseReal(&token);
1997 token += strcspn(token, "/ \t\r") + 1;
1998 }
1999
2000 tag.stringValues.resize(static_cast<size_t>(ts.num_strings));
2001 for (size_t i = 0; i < static_cast<size_t>(ts.num_strings); ++i) {
2002 char stringValueBuffer[4096];
2003
2004#ifdef _MSC_VER
2005 sscanf_s(token, "%s", stringValueBuffer,
2006 (unsigned)_countof(stringValueBuffer));
2007#else
2008 std::sscanf(token, "%s", stringValueBuffer);
2009#endif
2010 tag.stringValues[i] = stringValueBuffer;
2011 token += tag.stringValues[i].size() + 1;
2012 }
2013
2014 tags.push_back(tag);
2015 }
2016#endif
2017
2018 // Ignore unknown command.
2019 }
2020
2021 if (err) {
2022 (*err) += errss.str();
2023 }
2024
2025 return true;
2026}
2027} // namespace tinyobj
2028
2029#endif
Definition tiny_obj_loader.h:278
Definition tiny_obj_loader.h:267
Definition tiny_obj_loader.h:291
GLM_FUNC_DECL vec< L, T, Q > sign(vec< L, T, Q > const &x)
Definition func_common.inl:295
GLM_FUNC_DECL GLM_CONSTEXPR genType e()
Definition constants.inl:102
@ key
the parser read a key of a value in an object
Definition tiny_obj_loader.h:226
Definition tiny_obj_loader.h:232
Definition tiny_obj_loader.h:205
Definition tiny_obj_loader.h:135
Definition tiny_obj_loader.h:211
Definition tiny_obj_loader.h:220
Definition tiny_obj_loader.h:195
Definition tiny_obj_loader.h:119