GCC Code Coverage Report


Directory: ./
File: src/tide/dependencies/tinyobjloader/tiny_obj_loader.h
Date: 2023-04-27 00:55:30
Exec Total Coverage
Lines: 0 1306 0.0%
Functions: 0 44 0.0%
Branches: 0 1694 0.0%

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