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