tiny_obj 0.1.0

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