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