/src/tinyobjloader/tiny_obj_loader.h
Line | Count | Source |
1 | | /* |
2 | | The MIT License (MIT) |
3 | | |
4 | | Copyright (c) 2012-Present, Syoyo Fujita and many contributors. |
5 | | |
6 | | Permission is hereby granted, free of charge, to any person obtaining a copy |
7 | | of this software and associated documentation files (the "Software"), to deal |
8 | | in the Software without restriction, including without limitation the rights |
9 | | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
10 | | copies of the Software, and to permit persons to whom the Software is |
11 | | furnished to do so, subject to the following conditions: |
12 | | |
13 | | The above copyright notice and this permission notice shall be included in |
14 | | all copies or substantial portions of the Software. |
15 | | |
16 | | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
17 | | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
18 | | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
19 | | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
20 | | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
21 | | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
22 | | THE SOFTWARE. |
23 | | */ |
24 | | |
25 | | // |
26 | | // version 2.0.0 : Add new object oriented API. 1.x API is still provided. |
27 | | // * 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 <algorithm> |
68 | | #include <map> |
69 | | #include <string> |
70 | | #include <vector> |
71 | | |
72 | | #include <cstdint> |
73 | | #include <cstring> |
74 | | #include <memory> |
75 | | #include <new> |
76 | | #include <type_traits> |
77 | | |
78 | | namespace tinyobj { |
79 | | |
80 | | // C++11 is now the minimum required standard. |
81 | | #if __cplusplus < 201103L && (!defined(_MSVC_LANG) || _MSVC_LANG < 201103L) |
82 | | #error "tinyobjloader requires C++11 or later. Compile with -std=c++11 or higher." |
83 | | #endif |
84 | | |
85 | | #ifdef __clang__ |
86 | | #pragma clang diagnostic push |
87 | | #if __has_warning("-Wzero-as-null-pointer-constant") |
88 | | #pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant" |
89 | | #endif |
90 | | |
91 | | #pragma clang diagnostic ignored "-Wpadded" |
92 | | |
93 | | #endif |
94 | | |
95 | | // https://en.wikipedia.org/wiki/Wavefront_.obj_file says ... |
96 | | // |
97 | | // -blendu on | off # set horizontal texture blending |
98 | | // (default on) |
99 | | // -blendv on | off # set vertical texture blending |
100 | | // (default on) |
101 | | // -boost real_value # boost mip-map sharpness |
102 | | // -mm base_value gain_value # modify texture map values (default |
103 | | // 0 1) |
104 | | // # base_value = brightness, |
105 | | // gain_value = contrast |
106 | | // -o u [v [w]] # Origin offset (default |
107 | | // 0 0 0) |
108 | | // -s u [v [w]] # Scale (default |
109 | | // 1 1 1) |
110 | | // -t u [v [w]] # Turbulence (default |
111 | | // 0 0 0) |
112 | | // -texres resolution # texture resolution to create |
113 | | // -clamp on | off # only render texels in the clamped |
114 | | // 0-1 range (default off) |
115 | | // # When unclamped, textures are |
116 | | // repeated across a surface, |
117 | | // # when clamped, only texels which |
118 | | // fall within the 0-1 |
119 | | // # range are rendered. |
120 | | // -bm mult_value # bump multiplier (for bump maps |
121 | | // only) |
122 | | // |
123 | | // -imfchan r | g | b | m | l | z # specifies which channel of the file |
124 | | // is used to |
125 | | // # create a scalar or bump texture. |
126 | | // r:red, g:green, |
127 | | // # b:blue, m:matte, l:luminance, |
128 | | // z:z-depth.. |
129 | | // # (the default for bump is 'l' and |
130 | | // for decal is 'm') |
131 | | // bump -imfchan r bumpmap.tga # says to use the red channel of |
132 | | // bumpmap.tga as the bumpmap |
133 | | // |
134 | | // For reflection maps... |
135 | | // |
136 | | // -type sphere # specifies a sphere for a "refl" |
137 | | // reflection map |
138 | | // -type cube_top | cube_bottom | # when using a cube map, the texture |
139 | | // file for each |
140 | | // cube_front | cube_back | # side of the cube is specified |
141 | | // separately |
142 | | // cube_left | cube_right |
143 | | // |
144 | | // TinyObjLoader extension. |
145 | | // |
146 | | // -colorspace SPACE # Color space of the texture. e.g. |
147 | | // 'sRGB` or 'linear' |
148 | | // |
149 | | |
150 | | #ifdef TINYOBJLOADER_USE_DOUBLE |
151 | | //#pragma message "using double" |
152 | | typedef double real_t; |
153 | | #else |
154 | | //#pragma message "using float" |
155 | | typedef float real_t; |
156 | | #endif |
157 | | |
158 | | typedef enum { |
159 | | TEXTURE_TYPE_NONE, // default |
160 | | TEXTURE_TYPE_SPHERE, |
161 | | TEXTURE_TYPE_CUBE_TOP, |
162 | | TEXTURE_TYPE_CUBE_BOTTOM, |
163 | | TEXTURE_TYPE_CUBE_FRONT, |
164 | | TEXTURE_TYPE_CUBE_BACK, |
165 | | TEXTURE_TYPE_CUBE_LEFT, |
166 | | TEXTURE_TYPE_CUBE_RIGHT |
167 | | } texture_type_t; |
168 | | |
169 | | struct texture_option_t { |
170 | | texture_type_t type; // -type (default TEXTURE_TYPE_NONE) |
171 | | real_t sharpness; // -boost (default 1.0?) |
172 | | real_t brightness; // base_value in -mm option (default 0) |
173 | | real_t contrast; // gain_value in -mm option (default 1) |
174 | | real_t origin_offset[3]; // -o u [v [w]] (default 0 0 0) |
175 | | real_t scale[3]; // -s u [v [w]] (default 1 1 1) |
176 | | real_t turbulence[3]; // -t u [v [w]] (default 0 0 0) |
177 | | int texture_resolution; // -texres resolution (No default value in the spec. |
178 | | // We'll use -1) |
179 | | bool clamp; // -clamp (default false) |
180 | | char imfchan; // -imfchan (the default for bump is 'l' and for decal is 'm') |
181 | | bool blendu; // -blendu (default on) |
182 | | bool blendv; // -blendv (default on) |
183 | | real_t bump_multiplier; // -bm (for bump maps only, default 1.0) |
184 | | |
185 | | // extension |
186 | | std::string colorspace; // Explicitly specify color space of stored texel |
187 | | // value. Usually `sRGB` or `linear` (default empty). |
188 | | }; |
189 | | |
190 | | struct material_t { |
191 | | std::string name; |
192 | | |
193 | | real_t ambient[3]; |
194 | | real_t diffuse[3]; |
195 | | real_t specular[3]; |
196 | | real_t transmittance[3]; |
197 | | real_t emission[3]; |
198 | | real_t shininess; |
199 | | real_t ior; // index of refraction |
200 | | real_t dissolve; // 1 == opaque; 0 == fully transparent |
201 | | // illumination model (see http://www.fileformat.info/format/material/) |
202 | | int illum; |
203 | | |
204 | | int dummy; // Suppress padding warning. |
205 | | |
206 | | std::string ambient_texname; // map_Ka. For ambient or ambient occlusion. |
207 | | std::string diffuse_texname; // map_Kd |
208 | | std::string specular_texname; // map_Ks |
209 | | std::string specular_highlight_texname; // map_Ns |
210 | | std::string bump_texname; // map_bump, map_Bump, bump |
211 | | std::string displacement_texname; // disp |
212 | | std::string alpha_texname; // map_d |
213 | | std::string reflection_texname; // refl |
214 | | |
215 | | texture_option_t ambient_texopt; |
216 | | texture_option_t diffuse_texopt; |
217 | | texture_option_t specular_texopt; |
218 | | texture_option_t specular_highlight_texopt; |
219 | | texture_option_t bump_texopt; |
220 | | texture_option_t displacement_texopt; |
221 | | texture_option_t alpha_texopt; |
222 | | texture_option_t reflection_texopt; |
223 | | |
224 | | // PBR extension |
225 | | // http://exocortex.com/blog/extending_wavefront_mtl_to_support_pbr |
226 | | real_t roughness; // [0, 1] default 0 |
227 | | real_t metallic; // [0, 1] default 0 |
228 | | real_t sheen; // [0, 1] default 0 |
229 | | real_t clearcoat_thickness; // [0, 1] default 0 |
230 | | real_t clearcoat_roughness; // [0, 1] default 0 |
231 | | real_t anisotropy; // aniso. [0, 1] default 0 |
232 | | real_t anisotropy_rotation; // anisor. [0, 1] default 0 |
233 | | real_t pad0; |
234 | | std::string roughness_texname; // map_Pr |
235 | | std::string metallic_texname; // map_Pm |
236 | | std::string sheen_texname; // map_Ps |
237 | | std::string emissive_texname; // map_Ke |
238 | | std::string normal_texname; // norm. For normal mapping. |
239 | | |
240 | | texture_option_t roughness_texopt; |
241 | | texture_option_t metallic_texopt; |
242 | | texture_option_t sheen_texopt; |
243 | | texture_option_t emissive_texopt; |
244 | | texture_option_t normal_texopt; |
245 | | |
246 | | int pad2; |
247 | | |
248 | | std::map<std::string, std::string> unknown_parameter; |
249 | | |
250 | | #ifdef TINY_OBJ_LOADER_PYTHON_BINDING |
251 | | // For pybind11 |
252 | | std::array<double, 3> GetDiffuse() { |
253 | | std::array<double, 3> values; |
254 | | values[0] = double(diffuse[0]); |
255 | | values[1] = double(diffuse[1]); |
256 | | values[2] = double(diffuse[2]); |
257 | | |
258 | | return values; |
259 | | } |
260 | | |
261 | | std::array<double, 3> GetSpecular() { |
262 | | std::array<double, 3> values; |
263 | | values[0] = double(specular[0]); |
264 | | values[1] = double(specular[1]); |
265 | | values[2] = double(specular[2]); |
266 | | |
267 | | return values; |
268 | | } |
269 | | |
270 | | std::array<double, 3> GetTransmittance() { |
271 | | std::array<double, 3> values; |
272 | | values[0] = double(transmittance[0]); |
273 | | values[1] = double(transmittance[1]); |
274 | | values[2] = double(transmittance[2]); |
275 | | |
276 | | return values; |
277 | | } |
278 | | |
279 | | std::array<double, 3> GetEmission() { |
280 | | std::array<double, 3> values; |
281 | | values[0] = double(emission[0]); |
282 | | values[1] = double(emission[1]); |
283 | | values[2] = double(emission[2]); |
284 | | |
285 | | return values; |
286 | | } |
287 | | |
288 | | std::array<double, 3> GetAmbient() { |
289 | | std::array<double, 3> values; |
290 | | values[0] = double(ambient[0]); |
291 | | values[1] = double(ambient[1]); |
292 | | values[2] = double(ambient[2]); |
293 | | |
294 | | return values; |
295 | | } |
296 | | |
297 | | void SetDiffuse(std::array<double, 3> &a) { |
298 | | diffuse[0] = real_t(a[0]); |
299 | | diffuse[1] = real_t(a[1]); |
300 | | diffuse[2] = real_t(a[2]); |
301 | | } |
302 | | |
303 | | void SetAmbient(std::array<double, 3> &a) { |
304 | | ambient[0] = real_t(a[0]); |
305 | | ambient[1] = real_t(a[1]); |
306 | | ambient[2] = real_t(a[2]); |
307 | | } |
308 | | |
309 | | void SetSpecular(std::array<double, 3> &a) { |
310 | | specular[0] = real_t(a[0]); |
311 | | specular[1] = real_t(a[1]); |
312 | | specular[2] = real_t(a[2]); |
313 | | } |
314 | | |
315 | | void SetTransmittance(std::array<double, 3> &a) { |
316 | | transmittance[0] = real_t(a[0]); |
317 | | transmittance[1] = real_t(a[1]); |
318 | | transmittance[2] = real_t(a[2]); |
319 | | } |
320 | | |
321 | | std::string GetCustomParameter(const std::string &key) { |
322 | | std::map<std::string, std::string>::const_iterator it = |
323 | | unknown_parameter.find(key); |
324 | | |
325 | | if (it != unknown_parameter.end()) { |
326 | | return it->second; |
327 | | } |
328 | | return std::string(); |
329 | | } |
330 | | |
331 | | #endif |
332 | | }; |
333 | | |
334 | | template <typename Alloc = std::allocator<char>> |
335 | | struct basic_tag_t { |
336 | | using allocator_type = Alloc; |
337 | | using char_alloc = |
338 | | typename std::allocator_traits<Alloc>::template rebind_alloc<char>; |
339 | | using string_type = |
340 | | std::basic_string<char, std::char_traits<char>, char_alloc>; |
341 | | using int_alloc = |
342 | | typename std::allocator_traits<Alloc>::template rebind_alloc<int>; |
343 | | using real_alloc = |
344 | | typename std::allocator_traits<Alloc>::template rebind_alloc<real_t>; |
345 | | using string_alloc = |
346 | | typename std::allocator_traits<Alloc>::template rebind_alloc<string_type>; |
347 | | |
348 | | string_type name; |
349 | | std::vector<int, int_alloc> intValues; |
350 | | std::vector<real_t, real_alloc> floatValues; |
351 | | std::vector<string_type, string_alloc> stringValues; |
352 | | |
353 | | basic_tag_t() |
354 | 903k | : name(), |
355 | 903k | intValues(), |
356 | 903k | floatValues(), |
357 | 903k | stringValues() {} |
358 | | |
359 | | explicit basic_tag_t(const allocator_type &alloc) |
360 | | : name(char_alloc(alloc)), |
361 | | intValues(int_alloc(alloc)), |
362 | | floatValues(real_alloc(alloc)), |
363 | | stringValues(string_alloc(alloc)) {} |
364 | | |
365 | | template <typename OtherAlloc> |
366 | | basic_tag_t(const basic_tag_t<OtherAlloc> &rhs) : name(rhs.name) { |
367 | | intValues.assign(rhs.intValues.begin(), rhs.intValues.end()); |
368 | | floatValues.assign(rhs.floatValues.begin(), rhs.floatValues.end()); |
369 | | stringValues.assign(rhs.stringValues.begin(), rhs.stringValues.end()); |
370 | | } |
371 | | |
372 | | template <typename OtherAlloc> |
373 | | basic_tag_t(const basic_tag_t<OtherAlloc> &rhs, const allocator_type &alloc) |
374 | | : name(rhs.name.begin(), rhs.name.end(), char_alloc(alloc)), |
375 | | intValues(int_alloc(alloc)), |
376 | | floatValues(real_alloc(alloc)), |
377 | | stringValues(string_alloc(alloc)) { |
378 | | intValues.assign(rhs.intValues.begin(), rhs.intValues.end()); |
379 | | floatValues.assign(rhs.floatValues.begin(), rhs.floatValues.end()); |
380 | | for (size_t i = 0; i < rhs.stringValues.size(); i++) { |
381 | | stringValues.emplace_back(rhs.stringValues[i].begin(), |
382 | | rhs.stringValues[i].end(), char_alloc(alloc)); |
383 | | } |
384 | | } |
385 | | |
386 | | template <typename OtherAlloc> |
387 | | basic_tag_t &operator=(const basic_tag_t<OtherAlloc> &rhs) { |
388 | | name = rhs.name; |
389 | | intValues.assign(rhs.intValues.begin(), rhs.intValues.end()); |
390 | | floatValues.assign(rhs.floatValues.begin(), rhs.floatValues.end()); |
391 | | stringValues.assign(rhs.stringValues.begin(), rhs.stringValues.end()); |
392 | | return *this; |
393 | | } |
394 | | }; |
395 | | |
396 | | using tag_t = basic_tag_t<>; |
397 | | |
398 | | struct joint_and_weight_t { |
399 | | int joint_id; |
400 | | real_t weight; |
401 | | }; |
402 | | |
403 | | template <typename Alloc = std::allocator<char>> |
404 | | struct basic_skin_weight_t { |
405 | | using allocator_type = Alloc; |
406 | | using joint_weight_alloc = |
407 | | typename std::allocator_traits<Alloc>::template rebind_alloc< |
408 | | joint_and_weight_t>; |
409 | | |
410 | | int vertex_id; // Corresponding vertex index in `attrib_t::vertices`. |
411 | | // Compared to `index_t`, this index must be positive and |
412 | | // start with 0(does not allow relative indexing) |
413 | | std::vector<joint_and_weight_t, joint_weight_alloc> weightValues; |
414 | | |
415 | 79.3k | basic_skin_weight_t() : vertex_id(0) {} |
416 | | |
417 | | explicit basic_skin_weight_t(const allocator_type &alloc) |
418 | | : vertex_id(0), weightValues(joint_weight_alloc(alloc)) {} |
419 | | |
420 | | template <typename OtherAlloc> |
421 | | basic_skin_weight_t(const basic_skin_weight_t<OtherAlloc> &rhs) |
422 | | : vertex_id(rhs.vertex_id) { |
423 | | weightValues.assign(rhs.weightValues.begin(), rhs.weightValues.end()); |
424 | | } |
425 | | |
426 | | template <typename OtherAlloc> |
427 | | basic_skin_weight_t(const basic_skin_weight_t<OtherAlloc> &rhs, |
428 | | const allocator_type &alloc) |
429 | | : vertex_id(rhs.vertex_id), weightValues(joint_weight_alloc(alloc)) { |
430 | | weightValues.assign(rhs.weightValues.begin(), rhs.weightValues.end()); |
431 | | } |
432 | | |
433 | | template <typename OtherAlloc> |
434 | | basic_skin_weight_t &operator=(const basic_skin_weight_t<OtherAlloc> &rhs) { |
435 | | vertex_id = rhs.vertex_id; |
436 | | weightValues.assign(rhs.weightValues.begin(), rhs.weightValues.end()); |
437 | | return *this; |
438 | | } |
439 | | }; |
440 | | |
441 | | using skin_weight_t = basic_skin_weight_t<>; |
442 | | |
443 | | // Index struct to support different indices for vtx/normal/texcoord. |
444 | | // -1 means not used. |
445 | | struct index_t { |
446 | | int vertex_index; |
447 | | int normal_index; |
448 | | int texcoord_index; |
449 | | }; |
450 | | |
451 | | struct mesh_t { |
452 | | std::vector<index_t> indices; |
453 | | std::vector<unsigned int> |
454 | | num_face_vertices; // The number of vertices per |
455 | | // face. 3 = triangle, 4 = quad, ... |
456 | | std::vector<int> material_ids; // per-face material ID |
457 | | std::vector<unsigned int> smoothing_group_ids; // per-face smoothing group |
458 | | // ID(0 = off. positive value |
459 | | // = group id) |
460 | | std::vector<tag_t> tags; // SubD tag |
461 | | }; |
462 | | |
463 | | // struct path_t { |
464 | | // std::vector<int> indices; // pairs of indices for lines |
465 | | //}; |
466 | | |
467 | | struct lines_t { |
468 | | // Linear flattened indices. |
469 | | std::vector<index_t> indices; // indices for vertices(poly lines) |
470 | | std::vector<int> num_line_vertices; // The number of vertices per line. |
471 | | }; |
472 | | |
473 | | struct points_t { |
474 | | std::vector<index_t> indices; // indices for points |
475 | | }; |
476 | | |
477 | | struct shape_t { |
478 | | std::string name; |
479 | | mesh_t mesh; |
480 | | lines_t lines; |
481 | | points_t points; |
482 | | }; |
483 | | |
484 | | // Vertex attributes |
485 | | struct attrib_t { |
486 | | std::vector<real_t> vertices; // 'v'(xyz) |
487 | | |
488 | | // For backward compatibility, we store vertex weight in separate array. |
489 | | std::vector<real_t> vertex_weights; // 'v'(w) |
490 | | std::vector<real_t> normals; // 'vn' |
491 | | std::vector<real_t> texcoords; // 'vt'(uv) |
492 | | |
493 | | // For backward compatibility, we store texture coordinate 'w' in separate |
494 | | // array. |
495 | | std::vector<real_t> texcoord_ws; // 'vt'(w) |
496 | | std::vector<real_t> colors; // extension: vertex colors |
497 | | |
498 | | // |
499 | | // TinyObj extension. |
500 | | // |
501 | | |
502 | | // NOTE(syoyo): array index is based on the appearance order. |
503 | | // To get a corresponding skin weight for a specific vertex id `vid`, |
504 | | // Need to reconstruct a look up table: `skin_weight_t::vertex_id` == `vid` |
505 | | // (e.g. using std::map, std::unordered_map) |
506 | | std::vector<skin_weight_t> skin_weights; |
507 | | |
508 | 11.4k | attrib_t() {} |
509 | | |
510 | | // |
511 | | // For pybind11 |
512 | | // |
513 | 0 | const std::vector<real_t> &GetVertices() const { return vertices; } |
514 | | |
515 | 0 | const std::vector<real_t> &GetVertexWeights() const { return vertex_weights; } |
516 | | }; |
517 | | |
518 | | struct callback_t { |
519 | | // W is optional and set to 1 if there is no `w` item in `v` line |
520 | | void (*vertex_cb)(void *user_data, real_t x, real_t y, real_t z, real_t w); |
521 | | void (*vertex_color_cb)(void *user_data, real_t x, real_t y, real_t z, |
522 | | real_t r, real_t g, real_t b, bool has_color); |
523 | | void (*normal_cb)(void *user_data, real_t x, real_t y, real_t z); |
524 | | |
525 | | // y and z are optional and set to 0 if there is no `y` and/or `z` item(s) in |
526 | | // `vt` line. |
527 | | void (*texcoord_cb)(void *user_data, real_t x, real_t y, real_t z); |
528 | | |
529 | | // called per 'f' line. num_indices is the number of face indices(e.g. 3 for |
530 | | // triangle, 4 for quad) |
531 | | // 0 will be passed for undefined index in index_t members. |
532 | | void (*index_cb)(void *user_data, index_t *indices, int num_indices); |
533 | | // `name` material name, `material_id` = the array index of material_t[]. -1 |
534 | | // if |
535 | | // a material not found in .mtl |
536 | | void (*usemtl_cb)(void *user_data, const char *name, int material_id); |
537 | | // `materials` = parsed material data. |
538 | | void (*mtllib_cb)(void *user_data, const material_t *materials, |
539 | | int num_materials); |
540 | | // There may be multiple group names |
541 | | void (*group_cb)(void *user_data, const char **names, int num_names); |
542 | | void (*object_cb)(void *user_data, const char *name); |
543 | | |
544 | | callback_t() |
545 | | : vertex_cb(NULL), |
546 | | vertex_color_cb(NULL), |
547 | | normal_cb(NULL), |
548 | | texcoord_cb(NULL), |
549 | | index_cb(NULL), |
550 | | usemtl_cb(NULL), |
551 | | mtllib_cb(NULL), |
552 | | group_cb(NULL), |
553 | 0 | object_cb(NULL) {} |
554 | | }; |
555 | | |
556 | | class MaterialReader { |
557 | | public: |
558 | 11.4k | MaterialReader() {} |
559 | | virtual ~MaterialReader(); |
560 | | |
561 | | virtual bool operator()(const std::string &matId, |
562 | | std::vector<material_t> *materials, |
563 | | std::map<std::string, int> *matMap, std::string *warn, |
564 | | std::string *err) = 0; |
565 | | }; |
566 | | |
567 | | /// |
568 | | /// Read .mtl from a file. |
569 | | /// |
570 | | class MaterialFileReader : public MaterialReader { |
571 | | public: |
572 | | // Path could contain separator(';' in Windows, ':' in Posix) |
573 | | explicit MaterialFileReader(const std::string &mtl_basedir) |
574 | 0 | : m_mtlBaseDir(mtl_basedir) {} |
575 | 0 | virtual ~MaterialFileReader() override {} |
576 | | virtual bool operator()(const std::string &matId, |
577 | | std::vector<material_t> *materials, |
578 | | std::map<std::string, int> *matMap, std::string *warn, |
579 | | std::string *err) override; |
580 | | |
581 | | private: |
582 | | std::string m_mtlBaseDir; |
583 | | }; |
584 | | |
585 | | /// |
586 | | /// Read .mtl from a stream. |
587 | | /// |
588 | | class MaterialStreamReader : public MaterialReader { |
589 | | public: |
590 | | explicit MaterialStreamReader(std::istream &inStream) |
591 | 11.4k | : m_inStream(inStream) {} |
592 | 0 | virtual ~MaterialStreamReader() override {} |
593 | | virtual bool operator()(const std::string &matId, |
594 | | std::vector<material_t> *materials, |
595 | | std::map<std::string, int> *matMap, std::string *warn, |
596 | | std::string *err) override; |
597 | | |
598 | | private: |
599 | | std::istream &m_inStream; |
600 | | }; |
601 | | |
602 | | // v2 API |
603 | | struct ObjReaderConfig { |
604 | | bool triangulate; // triangulate polygon? |
605 | | |
606 | | // Currently not used. |
607 | | // "simple" or empty: Create triangle fan |
608 | | // "earcut": Use the algorithm based on Ear clipping |
609 | | std::string triangulation_method; |
610 | | |
611 | | /// Parse vertex color. |
612 | | /// If vertex color is not present, its filled with default value. |
613 | | /// false = no vertex color |
614 | | /// This will increase memory of parsed .obj |
615 | | bool vertex_color; |
616 | | |
617 | | /// |
618 | | /// Search path to .mtl file. |
619 | | /// Default = "" = search from the same directory of .obj file. |
620 | | /// Valid only when loading .obj from a file. |
621 | | /// |
622 | | std::string mtl_search_path; |
623 | | |
624 | | ObjReaderConfig() |
625 | 11.4k | : triangulate(true), triangulation_method("simple"), vertex_color(true) {} |
626 | | }; |
627 | | |
628 | | /// |
629 | | /// Wavefront .obj reader class(v2 API) |
630 | | /// |
631 | | class ObjReader { |
632 | | public: |
633 | 11.4k | ObjReader() : valid_(false) {} |
634 | | |
635 | | /// |
636 | | /// Load .obj and .mtl from a file. |
637 | | /// |
638 | | /// @param[in] filename wavefront .obj filename |
639 | | /// @param[in] config Reader configuration |
640 | | /// |
641 | | bool ParseFromFile(const std::string &filename, |
642 | | const ObjReaderConfig &config = ObjReaderConfig()); |
643 | | |
644 | | /// |
645 | | /// Parse .obj from a text string. |
646 | | /// Need to supply .mtl text string by `mtl_text`. |
647 | | /// This function ignores `mtllib` line in .obj text. |
648 | | /// |
649 | | /// @param[in] obj_text wavefront .obj filename |
650 | | /// @param[in] mtl_text wavefront .mtl filename |
651 | | /// @param[in] config Reader configuration |
652 | | /// |
653 | | bool ParseFromString(const std::string &obj_text, const std::string &mtl_text, |
654 | | const ObjReaderConfig &config = ObjReaderConfig()); |
655 | | |
656 | | /// |
657 | | /// .obj was loaded or parsed correctly. |
658 | | /// |
659 | 0 | bool Valid() const { return valid_; } |
660 | | |
661 | 0 | const attrib_t &GetAttrib() const { return attrib_; } |
662 | | |
663 | 0 | const std::vector<shape_t> &GetShapes() const { return shapes_; } |
664 | | |
665 | 0 | const std::vector<material_t> &GetMaterials() const { return materials_; } |
666 | | |
667 | | /// |
668 | | /// Warning message(may be filled after `Load` or `Parse`) |
669 | | /// |
670 | 0 | const std::string &Warning() const { return warning_; } |
671 | | |
672 | | /// |
673 | | /// Error message(filled when `Load` or `Parse` failed) |
674 | | /// |
675 | 0 | const std::string &Error() const { return error_; } |
676 | | |
677 | | private: |
678 | | bool valid_; |
679 | | |
680 | | attrib_t attrib_; |
681 | | std::vector<shape_t> shapes_; |
682 | | std::vector<material_t> materials_; |
683 | | |
684 | | std::string warning_; |
685 | | std::string error_; |
686 | | }; |
687 | | |
688 | | /// ==>>========= Legacy v1 API ============================================= |
689 | | |
690 | | /// Loads .obj from a file. |
691 | | /// 'attrib', 'shapes' and 'materials' will be filled with parsed shape data |
692 | | /// 'shapes' will be filled with parsed shape data |
693 | | /// Returns true when loading .obj become success. |
694 | | /// Returns warning message into `warn`, and error message into `err` |
695 | | /// 'mtl_basedir' is optional, and used for base directory for .mtl file. |
696 | | /// In default(`NULL'), .mtl file is searched from an application's working |
697 | | /// directory. |
698 | | /// 'triangulate' is optional, and used whether triangulate polygon face in .obj |
699 | | /// or not. |
700 | | /// Option 'default_vcols_fallback' specifies whether vertex colors should |
701 | | /// always be defined, even if no colors are given (fallback to white). |
702 | | bool LoadObj(attrib_t *attrib, std::vector<shape_t> *shapes, |
703 | | std::vector<material_t> *materials, std::string *warn, |
704 | | std::string *err, const char *filename, |
705 | | const char *mtl_basedir = NULL, bool triangulate = true, |
706 | | bool default_vcols_fallback = true); |
707 | | |
708 | | /// Loads .obj from a file with custom user callback. |
709 | | /// .mtl is loaded as usual and parsed material_t data will be passed to |
710 | | /// `callback.mtllib_cb`. |
711 | | /// Returns true when loading .obj/.mtl become success. |
712 | | /// Returns warning message into `warn`, and error message into `err` |
713 | | /// See `examples/callback_api/` for how to use this function. |
714 | | bool LoadObjWithCallback(std::istream &inStream, const callback_t &callback, |
715 | | void *user_data = NULL, |
716 | | MaterialReader *readMatFn = NULL, |
717 | | std::string *warn = NULL, std::string *err = NULL); |
718 | | |
719 | | /// Loads object from a std::istream, uses `readMatFn` to retrieve |
720 | | /// std::istream for materials. |
721 | | /// Returns true when loading .obj become success. |
722 | | /// Returns warning and error message into `err` |
723 | | bool LoadObj(attrib_t *attrib, std::vector<shape_t> *shapes, |
724 | | std::vector<material_t> *materials, std::string *warn, |
725 | | std::string *err, std::istream *inStream, |
726 | | MaterialReader *readMatFn = NULL, bool triangulate = true, |
727 | | bool default_vcols_fallback = true); |
728 | | |
729 | | /// Loads materials into std::map |
730 | | void LoadMtl(std::map<std::string, int> *material_map, |
731 | | std::vector<material_t> *materials, std::istream *inStream, |
732 | | std::string *warning, std::string *err); |
733 | | |
734 | | /// |
735 | | /// Parse texture name and texture option for custom texture parameter through |
736 | | /// material::unknown_parameter |
737 | | /// |
738 | | /// @param[out] texname Parsed texture name |
739 | | /// @param[out] texopt Parsed texopt |
740 | | /// @param[in] linebuf Input string |
741 | | /// |
742 | | bool ParseTextureNameAndOption(std::string *texname, texture_option_t *texopt, |
743 | | const char *linebuf); |
744 | | |
745 | | /// =<<========== Legacy v1 API ============================================= |
746 | | |
747 | | /// ==>>========= Optimized API (C++11 required) ============================ |
748 | | /// |
749 | | /// Optional compile options (all DISABLED by default — define the macro to |
750 | | /// opt in): |
751 | | /// TINYOBJLOADER_USE_MULTITHREADING - multi-threaded parsing |
752 | | /// TINYOBJLOADER_USE_SIMD - SIMD-accelerated line scanning |
753 | | /// (SSE2/AVX2 on x86, NEON on ARM) |
754 | | /// TINYOBJLOADER_ENABLE_EXCEPTION - throw std::bad_alloc on allocation |
755 | | /// failure instead of returning nullptr |
756 | | /// |
757 | | /// With none of these defined, the parser runs single-threaded, scalar, and |
758 | | /// exception-free. These features require C++11 or later. |
759 | | /// |
760 | | |
761 | | /// |
762 | | /// Arena-based memory allocator for reduced allocation overhead when |
763 | | /// loading huge meshes. Memory is freed in bulk when the arena is |
764 | | /// destroyed or reset(). Individual deallocate() calls are no-ops. |
765 | | /// |
766 | | class ArenaAllocator { |
767 | | public: |
768 | | explicit ArenaAllocator(size_t block_size = 1024 * 1024) |
769 | 0 | : head_(nullptr), default_block_size_(block_size) {} |
770 | | |
771 | 0 | ~ArenaAllocator() { destroy(); } |
772 | | |
773 | | ArenaAllocator(const ArenaAllocator &) = delete; |
774 | | ArenaAllocator &operator=(const ArenaAllocator &) = delete; |
775 | | |
776 | | ArenaAllocator(ArenaAllocator &&o) noexcept |
777 | 0 | : head_(o.head_), default_block_size_(o.default_block_size_) { |
778 | 0 | o.head_ = nullptr; |
779 | 0 | } |
780 | 0 | ArenaAllocator &operator=(ArenaAllocator &&o) noexcept { |
781 | 0 | if (this != &o) { |
782 | 0 | destroy(); |
783 | 0 | head_ = o.head_; |
784 | 0 | default_block_size_ = o.default_block_size_; |
785 | 0 | o.head_ = nullptr; |
786 | 0 | } |
787 | 0 | return *this; |
788 | 0 | } |
789 | | |
790 | | void *allocate(size_t bytes, |
791 | | size_t alignment = sizeof(void *)); |
792 | | |
793 | | /// Free all memory at once. |
794 | | void reset(); |
795 | | |
796 | | private: |
797 | | struct Block { |
798 | | unsigned char *data; |
799 | | size_t capacity; |
800 | | size_t used; |
801 | | Block *next; |
802 | | }; |
803 | | |
804 | | Block *head_; |
805 | | size_t default_block_size_; |
806 | | |
807 | | Block *new_block(size_t min_bytes); |
808 | | void destroy(); |
809 | | }; |
810 | | |
811 | | /// |
812 | | /// STL-compatible allocator adapter backed by an ArenaAllocator. |
813 | | /// deallocate() is a no-op — memory is released when the arena is reset. |
814 | | /// |
815 | | template <typename T> |
816 | | class arena_adapter { |
817 | | public: |
818 | | using value_type = T; |
819 | | using pointer = T *; |
820 | | using const_pointer = const T *; |
821 | | using size_type = std::size_t; |
822 | | using difference_type = std::ptrdiff_t; |
823 | | using propagate_on_container_copy_assignment = std::true_type; |
824 | | using propagate_on_container_move_assignment = std::true_type; |
825 | | using propagate_on_container_swap = std::true_type; |
826 | | |
827 | | explicit arena_adapter(ArenaAllocator *arena = nullptr) noexcept |
828 | | : arena_(arena) {} |
829 | | |
830 | | template <typename U> |
831 | | arena_adapter(const arena_adapter<U> &other) noexcept |
832 | | : arena_(other.arena()) {} |
833 | | |
834 | | T *allocate(size_t n) { |
835 | | if (n > SIZE_MAX / sizeof(T)) { |
836 | | #ifdef TINYOBJLOADER_ENABLE_EXCEPTION |
837 | | throw std::bad_alloc(); |
838 | | #else |
839 | | return nullptr; |
840 | | #endif |
841 | | } |
842 | | if (arena_) { |
843 | | return static_cast<T *>(arena_->allocate(n * sizeof(T), alignof(T))); |
844 | | } |
845 | | #ifdef TINYOBJLOADER_ENABLE_EXCEPTION |
846 | | return static_cast<T *>(::operator new(n * sizeof(T))); |
847 | | #else |
848 | | return static_cast<T *>(::operator new(n * sizeof(T), std::nothrow)); |
849 | | #endif |
850 | | } |
851 | | |
852 | | void deallocate(T *p, size_t) noexcept { |
853 | | if (!arena_) { |
854 | | ::operator delete(p); |
855 | | return; |
856 | | } |
857 | | // Arena deallocation is a no-op; memory freed in bulk via reset(). |
858 | | } |
859 | | |
860 | | ArenaAllocator *arena() const noexcept { return arena_; } |
861 | | |
862 | | template <typename U> |
863 | | bool operator==(const arena_adapter<U> &o) const noexcept { |
864 | | return arena_ == o.arena(); |
865 | | } |
866 | | template <typename U> |
867 | | bool operator!=(const arena_adapter<U> &o) const noexcept { |
868 | | return arena_ != o.arena(); |
869 | | } |
870 | | |
871 | | template <typename U> |
872 | | struct rebind { |
873 | | using other = arena_adapter<U>; |
874 | | }; |
875 | | |
876 | | private: |
877 | | ArenaAllocator *arena_; |
878 | | }; |
879 | | |
880 | | /// |
881 | | /// Template mesh type supporting custom allocators. |
882 | | /// Note: Alloc must be default-constructible (stateless). For stateful |
883 | | /// allocators like arena_adapter, construct vectors individually with |
884 | | /// an allocator instance. |
885 | | /// |
886 | | template <typename Alloc = std::allocator<char>> |
887 | | struct basic_mesh_t { |
888 | | using index_alloc = |
889 | | typename std::allocator_traits<Alloc>::template rebind_alloc<index_t>; |
890 | | using uint_alloc = |
891 | | typename std::allocator_traits<Alloc>::template rebind_alloc<unsigned int>; |
892 | | using int_alloc = |
893 | | typename std::allocator_traits<Alloc>::template rebind_alloc<int>; |
894 | | using tag_alloc = |
895 | | typename std::allocator_traits<Alloc>::template rebind_alloc< |
896 | | basic_tag_t<Alloc> >; |
897 | | |
898 | | std::vector<index_t, index_alloc> indices; |
899 | | std::vector<unsigned int, uint_alloc> num_face_vertices; |
900 | | std::vector<int, int_alloc> material_ids; |
901 | | std::vector<unsigned int, uint_alloc> smoothing_group_ids; |
902 | | std::vector<basic_tag_t<Alloc>, tag_alloc> tags; |
903 | | }; |
904 | | |
905 | | template <typename Alloc = std::allocator<char>> |
906 | | struct basic_lines_t { |
907 | | using index_alloc = |
908 | | typename std::allocator_traits<Alloc>::template rebind_alloc<index_t>; |
909 | | using int_alloc = |
910 | | typename std::allocator_traits<Alloc>::template rebind_alloc<int>; |
911 | | |
912 | | std::vector<index_t, index_alloc> indices; |
913 | | std::vector<int, int_alloc> num_line_vertices; |
914 | | }; |
915 | | |
916 | | template <typename Alloc = std::allocator<char>> |
917 | | struct basic_points_t { |
918 | | using index_alloc = |
919 | | typename std::allocator_traits<Alloc>::template rebind_alloc<index_t>; |
920 | | |
921 | | std::vector<index_t, index_alloc> indices; |
922 | | }; |
923 | | |
924 | | /// |
925 | | /// Template shape type supporting custom allocators. |
926 | | /// `name` always uses the default allocator; only mesh buffers are |
927 | | /// allocator-aware. |
928 | | /// |
929 | | template <typename Alloc = std::allocator<char>> |
930 | | struct basic_shape_t { |
931 | | std::string name; |
932 | | basic_mesh_t<Alloc> mesh; |
933 | | basic_lines_t<Alloc> lines; |
934 | | basic_points_t<Alloc> points; |
935 | | }; |
936 | | |
937 | | /// |
938 | | /// Template attrib type supporting custom allocators. |
939 | | /// Flat arrays: vertices(xyz), normals(xyz), texcoords(uv). |
940 | | /// Note: Alloc must be default-constructible (stateless). For stateful |
941 | | /// allocators like arena_adapter, construct vectors individually with |
942 | | /// an allocator instance. |
943 | | /// |
944 | | template <typename Alloc = std::allocator<char>> |
945 | | struct basic_attrib_t { |
946 | | using real_alloc = |
947 | | typename std::allocator_traits<Alloc>::template rebind_alloc<real_t>; |
948 | | using int_alloc = |
949 | | typename std::allocator_traits<Alloc>::template rebind_alloc<int>; |
950 | | using index_alloc = |
951 | | typename std::allocator_traits<Alloc>::template rebind_alloc<index_t>; |
952 | | using skin_weight_alloc = |
953 | | typename std::allocator_traits<Alloc>::template rebind_alloc< |
954 | | basic_skin_weight_t<Alloc> >; |
955 | | |
956 | | std::vector<real_t, real_alloc> vertices; // xyz |
957 | | std::vector<real_t, real_alloc> vertex_weights; // optional w for `v` |
958 | | std::vector<real_t, real_alloc> normals; // xyz |
959 | | std::vector<real_t, real_alloc> texcoords; // uv |
960 | | std::vector<real_t, real_alloc> texcoord_ws; // optional w for `vt` |
961 | | std::vector<real_t, real_alloc> colors; // rgb (optional) |
962 | | std::vector<basic_skin_weight_t<Alloc>, skin_weight_alloc> skin_weights; |
963 | | std::vector<index_t, index_alloc> indices; // face indices |
964 | | std::vector<int, int_alloc> face_num_verts; // verts per face |
965 | | std::vector<int, int_alloc> material_ids; // per-face material |
966 | | }; |
967 | | |
968 | | /// |
969 | | /// Configuration for the optimized loader. |
970 | | /// |
971 | | struct OptLoadConfig { |
972 | | /// Number of threads. -1 = hardware_concurrency, 0 or 1 = single-threaded. |
973 | | /// Effective only when TINYOBJLOADER_USE_MULTITHREADING is defined. |
974 | | int num_threads; |
975 | | |
976 | | bool triangulate; ///< Triangulate polygons (fan triangulation). |
977 | | bool verbose; ///< Reserved for future use (currently has no effect). |
978 | | |
979 | | /// Enable trie-based float string → value cache. |
980 | | /// Speeds up files with many repeated coordinate values (e.g. "0.0", "1.0"). |
981 | | bool float_cache; |
982 | | |
983 | | /// Maximum trie nodes per thread (controls cache memory). |
984 | | /// Each node is ~36 bytes, so 1024 nodes ≈ 36 KB (fits in L1 cache). |
985 | | int float_cache_max_nodes; |
986 | | |
987 | | /// Use fp32-length keys for the float cache (max 8 chars). |
988 | | /// When true, only tokens up to `float::digits10 + 2` characters are cached. |
989 | | /// When false, uses `real_t::digits10 + 2` (longer keys, fewer hits). |
990 | | /// Has no effect when float_cache is false. |
991 | | bool fp32_cache; |
992 | | |
993 | | OptLoadConfig() |
994 | | : num_threads(-1), triangulate(true), verbose(false), |
995 | 0 | float_cache(false), float_cache_max_nodes(1024), fp32_cache(true) {} |
996 | | }; |
997 | | |
998 | | /// Optimized loader — parse from a raw memory buffer. |
999 | | /// Supports multi-threading (TINYOBJLOADER_USE_MULTITHREADING) and |
1000 | | /// SIMD line scanning (TINYOBJLOADER_USE_SIMD). |
1001 | | bool LoadObjOpt(basic_attrib_t<> *attrib, |
1002 | | std::vector<basic_shape_t<>> *shapes, |
1003 | | std::vector<material_t> *materials, |
1004 | | std::string *warn, std::string *err, |
1005 | | const char *buf, size_t buf_len, |
1006 | | const OptLoadConfig &config = OptLoadConfig()); |
1007 | | |
1008 | | /// Optimized loader — load from a file. |
1009 | | bool LoadObjOpt(basic_attrib_t<> *attrib, |
1010 | | std::vector<basic_shape_t<>> *shapes, |
1011 | | std::vector<material_t> *materials, |
1012 | | std::string *warn, std::string *err, |
1013 | | const char *filename, |
1014 | | const char *mtl_basedir = nullptr, |
1015 | | const OptLoadConfig &config = OptLoadConfig()); |
1016 | | |
1017 | | // ---- TypedArray-based zero-copy API for maximum efficiency ---- |
1018 | | |
1019 | | /// |
1020 | | /// Non-owning, contiguous array view allocated from an ArenaAllocator. |
1021 | | /// No capacity tracking, no realloc. Lifetime is tied to the arena. |
1022 | | /// |
1023 | | template <typename T> |
1024 | | class TypedArray { |
1025 | | static_assert(std::is_trivially_copyable<T>::value, |
1026 | | "TypedArray<T> requires T to be trivially copyable " |
1027 | | "(uses memset for initialization)."); |
1028 | | |
1029 | | public: |
1030 | 0 | TypedArray() : data_(nullptr), size_(0) {}Unexecuted instantiation: tinyobj::TypedArray<float>::TypedArray() Unexecuted instantiation: tinyobj::TypedArray<tinyobj::index_t>::TypedArray() Unexecuted instantiation: tinyobj::TypedArray<int>::TypedArray() Unexecuted instantiation: tinyobj::TypedArray<unsigned int>::TypedArray() Unexecuted instantiation: tinyobj::TypedArray<unsigned long>::TypedArray() |
1031 | | |
1032 | 0 | T *data() { return data_; }Unexecuted instantiation: tinyobj::TypedArray<float>::data() Unexecuted instantiation: tinyobj::TypedArray<tinyobj::index_t>::data() Unexecuted instantiation: tinyobj::TypedArray<int>::data() |
1033 | | const T *data() const { return data_; } |
1034 | 0 | size_t size() const { return size_; }Unexecuted instantiation: tinyobj::TypedArray<float>::size() const Unexecuted instantiation: tinyobj::TypedArray<int>::size() const |
1035 | | bool empty() const { return size_ == 0; } |
1036 | | |
1037 | 0 | T &operator[](size_t i) { return data_[i]; }Unexecuted instantiation: tinyobj::TypedArray<unsigned int>::operator[](unsigned long) Unexecuted instantiation: tinyobj::TypedArray<float>::operator[](unsigned long) Unexecuted instantiation: tinyobj::TypedArray<int>::operator[](unsigned long) Unexecuted instantiation: tinyobj::TypedArray<tinyobj::index_t>::operator[](unsigned long) Unexecuted instantiation: tinyobj::TypedArray<unsigned long>::operator[](unsigned long) |
1038 | | const T &operator[](size_t i) const { return data_[i]; } |
1039 | | |
1040 | | T *begin() { return data_; } |
1041 | | T *end() { return data_ + size_; } |
1042 | | const T *begin() const { return data_; } |
1043 | | const T *end() const { return data_ + size_; } |
1044 | | |
1045 | | /// Allocate `count` elements from `arena`. Previous contents are abandoned. |
1046 | | /// Elements are zero-initialized via memset (T must be trivially copyable). |
1047 | | /// Returns false if allocation fails (only possible when exceptions are |
1048 | | /// disabled via TINYOBJLOADER_ENABLE_EXCEPTION not being defined). |
1049 | 0 | bool allocate(ArenaAllocator &arena, size_t count) { |
1050 | 0 | if (count == 0) { |
1051 | 0 | data_ = nullptr; |
1052 | 0 | size_ = 0; |
1053 | 0 | return true; |
1054 | 0 | } |
1055 | | // Guard against size_t overflow in count * sizeof(T). |
1056 | 0 | if (count > SIZE_MAX / sizeof(T)) { |
1057 | | #ifdef TINYOBJLOADER_ENABLE_EXCEPTION |
1058 | | throw std::bad_alloc(); |
1059 | | #else |
1060 | 0 | data_ = nullptr; |
1061 | 0 | size_ = 0; |
1062 | 0 | return false; |
1063 | 0 | #endif |
1064 | 0 | } |
1065 | 0 | void *p = arena.allocate(count * sizeof(T), alignof(T)); |
1066 | 0 | if (!p) { |
1067 | 0 | data_ = nullptr; |
1068 | 0 | size_ = 0; |
1069 | 0 | return false; |
1070 | 0 | } |
1071 | 0 | data_ = static_cast<T *>(p); |
1072 | 0 | size_ = count; |
1073 | 0 | std::memset(data_, 0, count * sizeof(T)); |
1074 | 0 | return true; |
1075 | 0 | } Unexecuted instantiation: tinyobj::TypedArray<float>::allocate(tinyobj::ArenaAllocator&, unsigned long) Unexecuted instantiation: tinyobj::TypedArray<tinyobj::index_t>::allocate(tinyobj::ArenaAllocator&, unsigned long) Unexecuted instantiation: tinyobj::TypedArray<int>::allocate(tinyobj::ArenaAllocator&, unsigned long) Unexecuted instantiation: tinyobj::TypedArray<unsigned int>::allocate(tinyobj::ArenaAllocator&, unsigned long) Unexecuted instantiation: tinyobj::TypedArray<unsigned long>::allocate(tinyobj::ArenaAllocator&, unsigned long) |
1076 | | |
1077 | | /// Wrap an existing arena-allocated pointer. Caller must ensure ptr |
1078 | | /// points into a live arena and count is within bounds. |
1079 | | void set(T *ptr, size_t count) { |
1080 | | data_ = ptr; |
1081 | | size_ = count; |
1082 | | } |
1083 | | |
1084 | | /// Shrink the logical size (no memory freed — arena owns it). |
1085 | 0 | void truncate(size_t new_size) { |
1086 | 0 | if (new_size < size_) size_ = new_size; |
1087 | 0 | } Unexecuted instantiation: tinyobj::TypedArray<tinyobj::index_t>::truncate(unsigned long) Unexecuted instantiation: tinyobj::TypedArray<int>::truncate(unsigned long) Unexecuted instantiation: tinyobj::TypedArray<unsigned int>::truncate(unsigned long) |
1088 | | |
1089 | | void clear() { |
1090 | | data_ = nullptr; |
1091 | | size_ = 0; |
1092 | | } |
1093 | | |
1094 | | private: |
1095 | | T *data_; |
1096 | | size_t size_; |
1097 | | }; |
1098 | | |
1099 | | /// |
1100 | | /// A shape expressed as a range into the flat attrib arrays. |
1101 | | /// No owned memory — just offsets + counts. |
1102 | | /// |
1103 | | struct OptShapeRange { |
1104 | | std::string name; |
1105 | | size_t face_offset; ///< Start index into OptAttrib::face_num_verts / material_ids |
1106 | | size_t face_count; ///< Number of faces in this shape |
1107 | | size_t index_offset; ///< Start index into OptAttrib::indices |
1108 | | size_t index_count; ///< Number of index_t entries for this shape |
1109 | | |
1110 | | OptShapeRange() |
1111 | 0 | : face_offset(0), face_count(0), index_offset(0), index_count(0) {} |
1112 | | }; |
1113 | | |
1114 | | /// |
1115 | | /// Flat attribute storage using TypedArray (arena-backed). |
1116 | | /// All arrays are allocated from the OptResult's arena. |
1117 | | /// |
1118 | | /// Unlike basic_attrib_t (used by LoadObjOpt), optional arrays like |
1119 | | /// vertex_weights, texcoord_ws, colors, and smoothing_group_ids are only |
1120 | | /// allocated when the input actually contains the corresponding data. |
1121 | | /// Check .empty() before accessing. |
1122 | | /// |
1123 | | struct OptAttrib { |
1124 | | TypedArray<real_t> vertices; ///< xyz, length = num_vertices * 3 |
1125 | | TypedArray<real_t> vertex_weights; ///< w per vertex (empty if no `v` line has w) |
1126 | | TypedArray<real_t> normals; ///< xyz, length = num_normals * 3 |
1127 | | TypedArray<real_t> texcoords; ///< uv, length = num_texcoords * 2 |
1128 | | TypedArray<real_t> texcoord_ws; ///< w per texcoord (empty if no `vt` has w) |
1129 | | TypedArray<real_t> colors; ///< rgb per vertex (empty if not all verts have color) |
1130 | | |
1131 | | TypedArray<index_t> indices; ///< flattened face indices |
1132 | | TypedArray<int> face_num_verts; ///< vertices per face |
1133 | | TypedArray<int> material_ids; ///< per-face material id |
1134 | | TypedArray<unsigned int> smoothing_group_ids; ///< per-face smoothing group |
1135 | | }; |
1136 | | |
1137 | | /// |
1138 | | /// Complete result from the TypedArray-based optimized loader. |
1139 | | /// Owns the ArenaAllocator — all TypedArray pointers are valid as long |
1140 | | /// as this object is alive. Move-only. |
1141 | | /// |
1142 | | struct OptResult { |
1143 | | ArenaAllocator arena; |
1144 | | OptAttrib attrib; |
1145 | | std::vector<OptShapeRange> shapes; ///< views into attrib arrays |
1146 | | std::vector<material_t> materials; |
1147 | | bool valid; |
1148 | | |
1149 | 0 | OptResult() : arena(4 * 1024 * 1024), valid(false) {} |
1150 | | |
1151 | | OptResult(OptResult &&o) noexcept |
1152 | | : arena(std::move(o.arena)), |
1153 | | attrib(o.attrib), |
1154 | | shapes(std::move(o.shapes)), |
1155 | | materials(std::move(o.materials)), |
1156 | 0 | valid(o.valid) { |
1157 | 0 | o.attrib = OptAttrib(); // clear dangling pointers |
1158 | 0 | o.valid = false; |
1159 | 0 | } |
1160 | | |
1161 | 0 | OptResult &operator=(OptResult &&o) noexcept { |
1162 | 0 | if (this != &o) { |
1163 | 0 | arena = std::move(o.arena); |
1164 | 0 | attrib = o.attrib; |
1165 | 0 | shapes = std::move(o.shapes); |
1166 | 0 | materials = std::move(o.materials); |
1167 | 0 | valid = o.valid; |
1168 | 0 | o.attrib = OptAttrib(); |
1169 | 0 | o.valid = false; |
1170 | 0 | } |
1171 | 0 | return *this; |
1172 | 0 | } |
1173 | | |
1174 | | OptResult(const OptResult &) = delete; |
1175 | | OptResult &operator=(const OptResult &) = delete; |
1176 | | }; |
1177 | | |
1178 | | /// TypedArray-based optimized loader — parse from a raw memory buffer. |
1179 | | /// Returns OptResult that owns all allocated memory via its arena. |
1180 | | OptResult LoadObjOptTyped(const char *buf, size_t buf_len, |
1181 | | std::string *warn, std::string *err, |
1182 | | const OptLoadConfig &config = OptLoadConfig()); |
1183 | | |
1184 | | /// TypedArray-based optimized loader — load from a file. |
1185 | | OptResult LoadObjOptTyped(const char *filename, |
1186 | | std::string *warn, std::string *err, |
1187 | | const char *mtl_basedir = nullptr, |
1188 | | const OptLoadConfig &config = OptLoadConfig()); |
1189 | | |
1190 | | /// =<<========== Optimized API ============================================= |
1191 | | |
1192 | | } // namespace tinyobj |
1193 | | |
1194 | | #endif // TINY_OBJ_LOADER_H_ |
1195 | | |
1196 | | // Guard the implementation against double inclusion within a single |
1197 | | // translation unit (e.g. when another header that also `#include`s this file |
1198 | | // is pulled in after TINYOBJLOADER_IMPLEMENTATION is defined). |
1199 | | #if defined(TINYOBJLOADER_IMPLEMENTATION) && \ |
1200 | | !defined(TINYOBJLOADER_IMPLEMENTATION_DEFINED) |
1201 | | #define TINYOBJLOADER_IMPLEMENTATION_DEFINED |
1202 | | #include <cassert> |
1203 | | #include <cctype> |
1204 | | #include <climits> |
1205 | | #include <cmath> |
1206 | | #include <cstddef> |
1207 | | #include <cstdint> |
1208 | | #include <cerrno> |
1209 | | #include <cstdlib> |
1210 | | #include <cstring> |
1211 | | #include <fstream> |
1212 | | #include <limits> |
1213 | | |
1214 | | #ifdef _WIN32 |
1215 | | #ifndef WIN32_LEAN_AND_MEAN |
1216 | | #define WIN32_LEAN_AND_MEAN |
1217 | | #endif |
1218 | | #ifndef NOMINMAX |
1219 | | #define NOMINMAX |
1220 | | #endif |
1221 | | #include <windows.h> |
1222 | | #endif |
1223 | | |
1224 | | #ifdef TINYOBJLOADER_USE_MMAP |
1225 | | #if !defined(_WIN32) |
1226 | | // POSIX headers for mmap |
1227 | | #include <fcntl.h> |
1228 | | #include <sys/mman.h> |
1229 | | #include <sys/stat.h> |
1230 | | #include <unistd.h> |
1231 | | #endif |
1232 | | #endif // TINYOBJLOADER_USE_MMAP |
1233 | | #include <set> |
1234 | | #include <sstream> |
1235 | | #include <utility> |
1236 | | |
1237 | | #ifdef TINYOBJLOADER_USE_MAPBOX_EARCUT |
1238 | | |
1239 | | #ifdef TINYOBJLOADER_DONOT_INCLUDE_MAPBOX_EARCUT |
1240 | | // Assume earcut.hpp is included outside of tiny_obj_loader.h |
1241 | | #else |
1242 | | |
1243 | | #ifdef __clang__ |
1244 | | #pragma clang diagnostic push |
1245 | | #pragma clang diagnostic ignored "-Weverything" |
1246 | | #endif |
1247 | | |
1248 | | #include <array> |
1249 | | |
1250 | | #include "mapbox/earcut.hpp" |
1251 | | |
1252 | | #ifdef __clang__ |
1253 | | #pragma clang diagnostic pop |
1254 | | #endif |
1255 | | |
1256 | | #endif |
1257 | | |
1258 | | #endif // TINYOBJLOADER_USE_MAPBOX_EARCUT |
1259 | | |
1260 | | #ifdef _WIN32 |
1261 | | // Converts a UTF-8 encoded string to a UTF-16 wide string for use with |
1262 | | // Windows file APIs that support Unicode paths (including paths longer than |
1263 | | // MAX_PATH when combined with the extended-length path prefix). |
1264 | | static std::wstring UTF8ToWchar(const std::string &str) { |
1265 | | if (str.empty()) return std::wstring(); |
1266 | | int size_needed = |
1267 | | MultiByteToWideChar(CP_UTF8, 0, str.c_str(), |
1268 | | static_cast<int>(str.size()), NULL, 0); |
1269 | | if (size_needed == 0) return std::wstring(); |
1270 | | std::wstring wstr(static_cast<size_t>(size_needed), L'\0'); |
1271 | | int result = |
1272 | | MultiByteToWideChar(CP_UTF8, 0, str.c_str(), |
1273 | | static_cast<int>(str.size()), &wstr[0], size_needed); |
1274 | | if (result == 0) return std::wstring(); |
1275 | | return wstr; |
1276 | | } |
1277 | | |
1278 | | // Prepends the Windows extended-length path prefix ("\\?\") to an absolute |
1279 | | // path when the path length meets or exceeds MAX_PATH (260 characters). |
1280 | | // This allows Windows APIs to handle paths up to 32767 characters long. |
1281 | | // UNC paths (starting with "\\") are converted to "\\?\UNC\" form. |
1282 | | static std::wstring LongPathW(const std::wstring &wpath) { |
1283 | | const std::wstring kLongPathPrefix = L"\\\\?\\"; |
1284 | | const std::wstring kUNCPrefix = L"\\\\"; |
1285 | | const std::wstring kLongUNCPathPrefix = L"\\\\?\\UNC\\"; |
1286 | | |
1287 | | // Already has the extended-length prefix; return as-is. |
1288 | | if (wpath.size() >= kLongPathPrefix.size() && |
1289 | | wpath.substr(0, kLongPathPrefix.size()) == kLongPathPrefix) { |
1290 | | return wpath; |
1291 | | } |
1292 | | |
1293 | | // Only add the prefix when the path is long enough to require it. |
1294 | | if (wpath.size() < MAX_PATH) { |
1295 | | return wpath; |
1296 | | } |
1297 | | |
1298 | | // Normalize forward slashes to backslashes: the extended-length "\\?\" |
1299 | | // prefix requires backslash separators only. |
1300 | | std::wstring normalized = wpath; |
1301 | | for (std::wstring::size_type i = 0; i < normalized.size(); ++i) { |
1302 | | if (normalized[i] == L'/') normalized[i] = L'\\'; |
1303 | | } |
1304 | | |
1305 | | // UNC path: "\\server\share\..." -> "\\?\UNC\server\share\..." |
1306 | | if (normalized.size() >= kUNCPrefix.size() && |
1307 | | normalized.substr(0, kUNCPrefix.size()) == kUNCPrefix) { |
1308 | | return kLongUNCPathPrefix + normalized.substr(kUNCPrefix.size()); |
1309 | | } |
1310 | | |
1311 | | // Absolute path with drive letter: "C:\..." -> "\\?\C:\..." |
1312 | | if (normalized.size() >= 2 && normalized[1] == L':') { |
1313 | | return kLongPathPrefix + normalized; |
1314 | | } |
1315 | | |
1316 | | return normalized; |
1317 | | } |
1318 | | #endif // _WIN32 |
1319 | | |
1320 | | #ifdef TINYOBJLOADER_USE_MULTITHREADING |
1321 | | #include <atomic> |
1322 | | #include <thread> |
1323 | | #endif |
1324 | | #ifdef TINYOBJLOADER_USE_SIMD |
1325 | | #if defined(__SSE2__) || defined(_M_X64) || defined(_M_AMD64) || \ |
1326 | | (defined(_M_IX86_FP) && _M_IX86_FP >= 2) |
1327 | | #define TINYOBJLOADER_SIMD_SSE2 1 |
1328 | | #include <emmintrin.h> |
1329 | | #if defined(__AVX2__) |
1330 | | #define TINYOBJLOADER_SIMD_AVX2 1 |
1331 | | #include <immintrin.h> |
1332 | | #endif |
1333 | | #elif defined(__ARM_NEON) || defined(__ARM_NEON__) |
1334 | | #define TINYOBJLOADER_SIMD_NEON 1 |
1335 | | #include <arm_neon.h> |
1336 | | #endif |
1337 | | #endif // TINYOBJLOADER_USE_SIMD |
1338 | | |
1339 | | // -------------------------------------------------------------------------- |
1340 | | // Embedded fast_float v8.0.2 for high-performance, bit-exact float parsing. |
1341 | | // Disable by defining TINYOBJLOADER_DISABLE_FAST_FLOAT before including |
1342 | | // this file with TINYOBJLOADER_IMPLEMENTATION. |
1343 | | // -------------------------------------------------------------------------- |
1344 | | #ifndef TINYOBJLOADER_DISABLE_FAST_FLOAT |
1345 | | |
1346 | | // Standard headers needed by the embedded fast_float. |
1347 | | #include <cfloat> |
1348 | | #include <cstdint> |
1349 | | |
1350 | | namespace tinyobj_ff { |
1351 | | |
1352 | | // --- integral_constant, true_type, false_type --- |
1353 | | template <typename T, T V> |
1354 | | struct integral_constant { |
1355 | | static const T value = V; |
1356 | | typedef T value_type; |
1357 | | typedef integral_constant type; |
1358 | | operator value_type() const { return value; } |
1359 | | }; |
1360 | | typedef integral_constant<bool, true> true_type; |
1361 | | typedef integral_constant<bool, false> false_type; |
1362 | | |
1363 | | // --- is_same --- |
1364 | | template <typename T, typename U> struct is_same : false_type {}; |
1365 | | template <typename T> struct is_same<T, T> : true_type {}; |
1366 | | |
1367 | | // --- enable_if --- |
1368 | | template <bool B, typename T = void> struct enable_if {}; |
1369 | | template <typename T> struct enable_if<true, T> { typedef T type; }; |
1370 | | |
1371 | | // --- conditional --- |
1372 | | template <bool B, typename T, typename F> struct conditional { typedef T type; }; |
1373 | | template <typename T, typename F> struct conditional<false, T, F> { typedef F type; }; |
1374 | | |
1375 | | // --- is_integral --- |
1376 | | template <typename T> struct is_integral : false_type {}; |
1377 | | template <> struct is_integral<bool> : true_type {}; |
1378 | | template <> struct is_integral<char> : true_type {}; |
1379 | | template <> struct is_integral<signed char> : true_type {}; |
1380 | | template <> struct is_integral<unsigned char> : true_type {}; |
1381 | | template <> struct is_integral<short> : true_type {}; |
1382 | | template <> struct is_integral<unsigned short> : true_type {}; |
1383 | | template <> struct is_integral<int> : true_type {}; |
1384 | | template <> struct is_integral<unsigned int> : true_type {}; |
1385 | | template <> struct is_integral<long> : true_type {}; |
1386 | | template <> struct is_integral<unsigned long> : true_type {}; |
1387 | | template <> struct is_integral<long long> : true_type {}; |
1388 | | template <> struct is_integral<unsigned long long> : true_type {}; |
1389 | | template <> struct is_integral<wchar_t> : true_type {}; |
1390 | | template <> struct is_integral<char16_t> : true_type {}; |
1391 | | template <> struct is_integral<char32_t> : true_type {}; |
1392 | | |
1393 | | // --- is_signed --- |
1394 | | template <typename T> struct is_signed : integral_constant<bool, T(-1) < T(0)> {}; |
1395 | | |
1396 | | // --- underlying_type (uses compiler builtin) --- |
1397 | | template <typename T> struct underlying_type { |
1398 | | typedef __underlying_type(T) type; |
1399 | | }; |
1400 | | |
1401 | | // --- ff_errc (replaces std::errc, our own enum - no system_error needed) --- |
1402 | | enum class ff_errc { ok = 0, invalid_argument = 22, result_out_of_range = 34 }; |
1403 | | |
1404 | | // --- min_val (replaces std::min, avoids Windows min/max macro conflicts) --- |
1405 | | template <typename T> |
1406 | 4.19k | inline T min_val(T a, T b) { return (b < a) ? b : a; } |
1407 | | |
1408 | | // --- copy_n --- |
1409 | | template <typename InputIt, typename Size, typename OutputIt> |
1410 | 243k | inline OutputIt copy_n(InputIt first, Size count, OutputIt result) { |
1411 | 2.72M | for (Size i = 0; i < count; ++i) *result++ = *first++; |
1412 | 243k | return result; |
1413 | 243k | } |
1414 | | |
1415 | | // --- copy_backward --- |
1416 | | template <typename BidirIt1, typename BidirIt2> |
1417 | 20.4k | inline BidirIt2 copy_backward(BidirIt1 first, BidirIt1 last, BidirIt2 d_last) { |
1418 | 313k | while (first != last) *(--d_last) = *(--last); |
1419 | 20.4k | return d_last; |
1420 | 20.4k | } |
1421 | | |
1422 | | // --- fill --- |
1423 | | template <typename ForwardIt, typename T> |
1424 | 213k | inline void fill(ForwardIt first, ForwardIt last, const T &value) { |
1425 | 529k | for (; first != last; ++first) *first = value; |
1426 | 213k | } void tinyobj_ff::fill<unsigned long*, unsigned long>(unsigned long*, unsigned long*, unsigned long const&) Line | Count | Source | 1424 | 193k | inline void fill(ForwardIt first, ForwardIt last, const T &value) { | 1425 | 388k | for (; first != last; ++first) *first = value; | 1426 | 193k | } |
void tinyobj_ff::fill<unsigned long*, int>(unsigned long*, unsigned long*, int const&) Line | Count | Source | 1424 | 20.4k | inline void fill(ForwardIt first, ForwardIt last, const T &value) { | 1425 | 141k | for (; first != last; ++first) *first = value; | 1426 | 20.4k | } |
|
1427 | | |
1428 | | // --- distance --- |
1429 | | template <typename It> |
1430 | | inline typename conditional<true, long long, It>::type |
1431 | 2.40M | distance(It first, It last) { |
1432 | 2.40M | return last - first; |
1433 | 2.40M | } |
1434 | | |
1435 | | } // namespace tinyobj_ff |
1436 | | |
1437 | | // --- Begin embedded fast_float v8.0.2 (MIT / Apache-2.0 / BSL-1.0) --- |
1438 | | // https://github.com/fastfloat/fast_float |
1439 | | // fast_float by Daniel Lemire |
1440 | | // fast_float by João Paulo Magalhaes |
1441 | | // |
1442 | | // |
1443 | | // with contributions from Eugene Golushkov |
1444 | | // with contributions from Maksim Kita |
1445 | | // with contributions from Marcin Wojdyr |
1446 | | // with contributions from Neal Richardson |
1447 | | // with contributions from Tim Paine |
1448 | | // with contributions from Fabio Pellacini |
1449 | | // with contributions from Lénárd Szolnoki |
1450 | | // with contributions from Jan Pharago |
1451 | | // with contributions from Maya Warrier |
1452 | | // with contributions from Taha Khokhar |
1453 | | // with contributions from Anders Dalvander |
1454 | | // |
1455 | | // |
1456 | | // Licensed under the Apache License, Version 2.0, or the |
1457 | | // MIT License or the Boost License. This file may not be copied, |
1458 | | // modified, or distributed except according to those terms. |
1459 | | // |
1460 | | // MIT License Notice |
1461 | | // |
1462 | | // MIT License |
1463 | | // |
1464 | | // Copyright (c) 2021 The fast_float authors |
1465 | | // |
1466 | | // Permission is hereby granted, free of charge, to any |
1467 | | // person obtaining a copy of this software and associated |
1468 | | // documentation files (the "Software"), to deal in the |
1469 | | // Software without restriction, including without |
1470 | | // limitation the rights to use, copy, modify, merge, |
1471 | | // publish, distribute, sublicense, and/or sell copies of |
1472 | | // the Software, and to permit persons to whom the Software |
1473 | | // is furnished to do so, subject to the following |
1474 | | // conditions: |
1475 | | // |
1476 | | // The above copyright notice and this permission notice |
1477 | | // shall be included in all copies or substantial portions |
1478 | | // of the Software. |
1479 | | // |
1480 | | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF |
1481 | | // ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED |
1482 | | // TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A |
1483 | | // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT |
1484 | | // SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY |
1485 | | // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION |
1486 | | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR |
1487 | | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
1488 | | // DEALINGS IN THE SOFTWARE. |
1489 | | // |
1490 | | // Apache License (Version 2.0) Notice |
1491 | | // |
1492 | | // Copyright 2021 The fast_float authors |
1493 | | // Licensed under the Apache License, Version 2.0 (the "License"); |
1494 | | // you may not use this file except in compliance with the License. |
1495 | | // You may obtain a copy of the License at |
1496 | | // |
1497 | | // http://www.apache.org/licenses/LICENSE-2.0 |
1498 | | // |
1499 | | // Unless required by applicable law or agreed to in writing, software |
1500 | | // distributed under the License is distributed on an "AS IS" BASIS, |
1501 | | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
1502 | | // See the License for the specific language governing permissions and |
1503 | | // |
1504 | | // BOOST License Notice |
1505 | | // |
1506 | | // Boost Software License - Version 1.0 - August 17th, 2003 |
1507 | | // |
1508 | | // Permission is hereby granted, free of charge, to any person or organization |
1509 | | // obtaining a copy of the software and accompanying documentation covered by |
1510 | | // this license (the "Software") to use, reproduce, display, distribute, |
1511 | | // execute, and transmit the Software, and to prepare derivative works of the |
1512 | | // Software, and to permit third-parties to whom the Software is furnished to |
1513 | | // do so, all subject to the following: |
1514 | | // |
1515 | | // The copyright notices in the Software and this entire statement, including |
1516 | | // the above license grant, this restriction and the following disclaimer, |
1517 | | // must be included in all copies of the Software, in whole or in part, and |
1518 | | // all derivative works of the Software, unless such copies or derivative |
1519 | | // works are solely in the form of machine-executable object code generated by |
1520 | | // a source language processor. |
1521 | | // |
1522 | | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
1523 | | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
1524 | | // FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT |
1525 | | // SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE |
1526 | | // FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, |
1527 | | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
1528 | | // DEALINGS IN THE SOFTWARE. |
1529 | | // |
1530 | | |
1531 | | #ifndef FASTFLOAT_CONSTEXPR_FEATURE_DETECT_H |
1532 | | #define FASTFLOAT_CONSTEXPR_FEATURE_DETECT_H |
1533 | | |
1534 | | #ifdef __has_include |
1535 | | #if __has_include(<version>) |
1536 | | #include <version> |
1537 | | #endif |
1538 | | #endif |
1539 | | |
1540 | | // Testing for https://wg21.link/N3652, adopted in C++14 |
1541 | | #if defined(__cpp_constexpr) && __cpp_constexpr >= 201304 |
1542 | | #define FASTFLOAT_CONSTEXPR14 constexpr |
1543 | | #else |
1544 | | #define FASTFLOAT_CONSTEXPR14 |
1545 | | #endif |
1546 | | |
1547 | | #if defined(__cpp_lib_bit_cast) && __cpp_lib_bit_cast >= 201806L |
1548 | | #define FASTFLOAT_HAS_BIT_CAST 1 |
1549 | | #else |
1550 | | #define FASTFLOAT_HAS_BIT_CAST 0 |
1551 | | #endif |
1552 | | |
1553 | | #if defined(__cpp_lib_is_constant_evaluated) && \ |
1554 | | __cpp_lib_is_constant_evaluated >= 201811L |
1555 | | #define FASTFLOAT_HAS_IS_CONSTANT_EVALUATED 1 |
1556 | | #else |
1557 | | #define FASTFLOAT_HAS_IS_CONSTANT_EVALUATED 0 |
1558 | | #endif |
1559 | | |
1560 | | #if defined(__cpp_if_constexpr) && __cpp_if_constexpr >= 201606L |
1561 | | #define FASTFLOAT_IF_CONSTEXPR17(x) if constexpr (x) |
1562 | | #else |
1563 | 3.71M | #define FASTFLOAT_IF_CONSTEXPR17(x) if (x) |
1564 | | #endif |
1565 | | |
1566 | | // Testing for relevant C++20 constexpr library features |
1567 | | #if FASTFLOAT_HAS_IS_CONSTANT_EVALUATED && FASTFLOAT_HAS_BIT_CAST && \ |
1568 | | defined(__cpp_lib_constexpr_algorithms) && \ |
1569 | | __cpp_lib_constexpr_algorithms >= 201806L /*For std::copy and std::fill*/ |
1570 | | #define FASTFLOAT_CONSTEXPR20 constexpr |
1571 | | #define FASTFLOAT_IS_CONSTEXPR 1 |
1572 | | #else |
1573 | | #define FASTFLOAT_CONSTEXPR20 |
1574 | | #define FASTFLOAT_IS_CONSTEXPR 0 |
1575 | | #endif |
1576 | | |
1577 | | #if __cplusplus >= 201703L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) |
1578 | | #define FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE 0 |
1579 | | #else |
1580 | | #define FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE 1 |
1581 | | #endif |
1582 | | |
1583 | | #endif // FASTFLOAT_CONSTEXPR_FEATURE_DETECT_H |
1584 | | |
1585 | | #ifndef FASTFLOAT_FLOAT_COMMON_H |
1586 | | #define FASTFLOAT_FLOAT_COMMON_H |
1587 | | |
1588 | | #include <cassert> |
1589 | | #include <cstring> |
1590 | | #include <limits> |
1591 | | #ifdef __has_include |
1592 | | #if __has_include(<stdfloat>) && (__cplusplus > 202002L || (defined(_MSVC_LANG) && (_MSVC_LANG > 202002L))) |
1593 | | #include <stdfloat> |
1594 | | #endif |
1595 | | #endif |
1596 | | |
1597 | | #define FASTFLOAT_VERSION_MAJOR 8 |
1598 | | #define FASTFLOAT_VERSION_MINOR 0 |
1599 | | #define FASTFLOAT_VERSION_PATCH 2 |
1600 | | |
1601 | | #define FASTFLOAT_STRINGIZE_IMPL(x) #x |
1602 | | #define FASTFLOAT_STRINGIZE(x) FASTFLOAT_STRINGIZE_IMPL(x) |
1603 | | |
1604 | | #define FASTFLOAT_VERSION_STR \ |
1605 | | FASTFLOAT_STRINGIZE(FASTFLOAT_VERSION_MAJOR) \ |
1606 | | "." FASTFLOAT_STRINGIZE(FASTFLOAT_VERSION_MINOR) "." FASTFLOAT_STRINGIZE( \ |
1607 | | FASTFLOAT_VERSION_PATCH) |
1608 | | |
1609 | | #define FASTFLOAT_VERSION \ |
1610 | | (FASTFLOAT_VERSION_MAJOR * 10000 + FASTFLOAT_VERSION_MINOR * 100 + \ |
1611 | | FASTFLOAT_VERSION_PATCH) |
1612 | | |
1613 | | namespace fast_float { |
1614 | | |
1615 | | enum class chars_format : uint64_t; |
1616 | | |
1617 | | namespace detail { |
1618 | | constexpr chars_format basic_json_fmt = chars_format(1 << 5); |
1619 | | constexpr chars_format basic_fortran_fmt = chars_format(1 << 6); |
1620 | | } // namespace detail |
1621 | | |
1622 | | enum class chars_format : uint64_t { |
1623 | | scientific = 1 << 0, |
1624 | | fixed = 1 << 2, |
1625 | | hex = 1 << 3, |
1626 | | no_infnan = 1 << 4, |
1627 | | // RFC 8259: https://datatracker.ietf.org/doc/html/rfc8259#section-6 |
1628 | | json = uint64_t(detail::basic_json_fmt) | fixed | scientific | no_infnan, |
1629 | | // Extension of RFC 8259 where, e.g., "inf" and "nan" are allowed. |
1630 | | json_or_infnan = uint64_t(detail::basic_json_fmt) | fixed | scientific, |
1631 | | fortran = uint64_t(detail::basic_fortran_fmt) | fixed | scientific, |
1632 | | general = fixed | scientific, |
1633 | | allow_leading_plus = 1 << 7, |
1634 | | skip_white_space = 1 << 8, |
1635 | | }; |
1636 | | |
1637 | | template <typename UC> struct from_chars_result_t { |
1638 | | UC const *ptr; |
1639 | | tinyobj_ff::ff_errc ec; |
1640 | | }; |
1641 | | |
1642 | | using from_chars_result = from_chars_result_t<char>; |
1643 | | |
1644 | | template <typename UC> struct parse_options_t { |
1645 | | constexpr explicit parse_options_t(chars_format fmt = chars_format::general, |
1646 | | UC dot = UC('.'), int b = 10) |
1647 | 1.87M | : format(fmt), decimal_point(dot), base(b) {} |
1648 | | |
1649 | | /** Which number formats are accepted */ |
1650 | | chars_format format; |
1651 | | /** The character used as decimal point */ |
1652 | | UC decimal_point; |
1653 | | /** The base used for integers */ |
1654 | | int base; |
1655 | | }; |
1656 | | |
1657 | | using parse_options = parse_options_t<char>; |
1658 | | |
1659 | | } // namespace fast_float |
1660 | | |
1661 | | #if FASTFLOAT_HAS_BIT_CAST |
1662 | | #include <bit> |
1663 | | #endif |
1664 | | |
1665 | | #if (defined(__x86_64) || defined(__x86_64__) || defined(_M_X64) || \ |
1666 | | defined(__amd64) || defined(__aarch64__) || defined(_M_ARM64) || \ |
1667 | | defined(__MINGW64__) || defined(__s390x__) || \ |
1668 | | (defined(__ppc64__) || defined(__PPC64__) || defined(__ppc64le__) || \ |
1669 | | defined(__PPC64LE__)) || \ |
1670 | | defined(__loongarch64)) |
1671 | | #define FASTFLOAT_64BIT 1 |
1672 | | #elif (defined(__i386) || defined(__i386__) || defined(_M_IX86) || \ |
1673 | | defined(__arm__) || defined(_M_ARM) || defined(__ppc__) || \ |
1674 | | defined(__MINGW32__) || defined(__EMSCRIPTEN__)) |
1675 | | #define FASTFLOAT_32BIT 1 |
1676 | | #else |
1677 | | // Need to check incrementally, since SIZE_MAX is a size_t, avoid overflow. |
1678 | | // We can never tell the register width, but the SIZE_MAX is a good |
1679 | | // approximation. UINTPTR_MAX and INTPTR_MAX are optional, so avoid them for max |
1680 | | // portability. |
1681 | | #if SIZE_MAX == 0xffff |
1682 | | #error Unknown platform (16-bit, unsupported) |
1683 | | #elif SIZE_MAX == 0xffffffff |
1684 | | #define FASTFLOAT_32BIT 1 |
1685 | | #elif SIZE_MAX == 0xffffffffffffffff |
1686 | | #define FASTFLOAT_64BIT 1 |
1687 | | #else |
1688 | | #error Unknown platform (not 32-bit, not 64-bit?) |
1689 | | #endif |
1690 | | #endif |
1691 | | |
1692 | | #if ((defined(_WIN32) || defined(_WIN64)) && !defined(__clang__)) || \ |
1693 | | (defined(_M_ARM64) && !defined(__MINGW32__)) |
1694 | | #include <intrin.h> |
1695 | | #endif |
1696 | | |
1697 | | #if defined(_MSC_VER) && !defined(__clang__) |
1698 | | #define FASTFLOAT_VISUAL_STUDIO 1 |
1699 | | #endif |
1700 | | |
1701 | | #if defined __BYTE_ORDER__ && defined __ORDER_BIG_ENDIAN__ |
1702 | | #define FASTFLOAT_IS_BIG_ENDIAN (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) |
1703 | | #elif defined _WIN32 |
1704 | | #define FASTFLOAT_IS_BIG_ENDIAN 0 |
1705 | | #else |
1706 | | #if defined(__APPLE__) || defined(__FreeBSD__) |
1707 | | #include <machine/endian.h> |
1708 | | #elif defined(sun) || defined(__sun) |
1709 | | #include <sys/byteorder.h> |
1710 | | #elif defined(__MVS__) |
1711 | | #include <sys/endian.h> |
1712 | | #else |
1713 | | #ifdef __has_include |
1714 | | #if __has_include(<endian.h>) |
1715 | | #include <endian.h> |
1716 | | #endif //__has_include(<endian.h>) |
1717 | | #endif //__has_include |
1718 | | #endif |
1719 | | # |
1720 | | #ifndef __BYTE_ORDER__ |
1721 | | // safe choice |
1722 | | #define FASTFLOAT_IS_BIG_ENDIAN 0 |
1723 | | #endif |
1724 | | # |
1725 | | #ifndef __ORDER_LITTLE_ENDIAN__ |
1726 | | // safe choice |
1727 | | #define FASTFLOAT_IS_BIG_ENDIAN 0 |
1728 | | #endif |
1729 | | # |
1730 | | #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ |
1731 | | #define FASTFLOAT_IS_BIG_ENDIAN 0 |
1732 | | #else |
1733 | | #define FASTFLOAT_IS_BIG_ENDIAN 1 |
1734 | | #endif |
1735 | | #endif |
1736 | | |
1737 | | #if defined(__SSE2__) || (defined(FASTFLOAT_VISUAL_STUDIO) && \ |
1738 | | (defined(_M_AMD64) || defined(_M_X64) || \ |
1739 | | (defined(_M_IX86_FP) && _M_IX86_FP == 2))) |
1740 | | #define FASTFLOAT_SSE2 1 |
1741 | | #endif |
1742 | | |
1743 | | #if defined(__aarch64__) || defined(_M_ARM64) |
1744 | | #define FASTFLOAT_NEON 1 |
1745 | | #endif |
1746 | | |
1747 | | #if defined(FASTFLOAT_SSE2) || defined(FASTFLOAT_NEON) |
1748 | | #define FASTFLOAT_HAS_SIMD 1 |
1749 | | #endif |
1750 | | |
1751 | | #if defined(__GNUC__) |
1752 | | // disable -Wcast-align=strict (GCC only) |
1753 | | #define FASTFLOAT_SIMD_DISABLE_WARNINGS \ |
1754 | | _Pragma("GCC diagnostic push") \ |
1755 | | _Pragma("GCC diagnostic ignored \"-Wcast-align\"") |
1756 | | #else |
1757 | | #define FASTFLOAT_SIMD_DISABLE_WARNINGS |
1758 | | #endif |
1759 | | |
1760 | | #if defined(__GNUC__) |
1761 | | #define FASTFLOAT_SIMD_RESTORE_WARNINGS _Pragma("GCC diagnostic pop") |
1762 | | #else |
1763 | | #define FASTFLOAT_SIMD_RESTORE_WARNINGS |
1764 | | #endif |
1765 | | |
1766 | | #ifdef FASTFLOAT_VISUAL_STUDIO |
1767 | | #define fastfloat_really_inline __forceinline |
1768 | | #else |
1769 | | #define fastfloat_really_inline inline __attribute__((always_inline)) |
1770 | | #endif |
1771 | | |
1772 | | #ifndef FASTFLOAT_ASSERT |
1773 | | #define FASTFLOAT_ASSERT(x) \ |
1774 | 106k | { ((void)(x)); } |
1775 | | #endif |
1776 | | |
1777 | | #ifndef FASTFLOAT_DEBUG_ASSERT |
1778 | | #define FASTFLOAT_DEBUG_ASSERT(x) \ |
1779 | 27.8M | { ((void)(x)); } |
1780 | | #endif |
1781 | | |
1782 | | // rust style `try!()` macro, or `?` operator |
1783 | | #define FASTFLOAT_TRY(x) \ |
1784 | 1.74M | { \ |
1785 | 1.74M | if (!(x)) \ |
1786 | 1.74M | return false; \ |
1787 | 1.74M | } |
1788 | | |
1789 | | #define FASTFLOAT_ENABLE_IF(...) \ |
1790 | | typename tinyobj_ff::enable_if<(__VA_ARGS__), int>::type |
1791 | | |
1792 | | namespace fast_float { |
1793 | | |
1794 | 0 | fastfloat_really_inline constexpr bool cpp20_and_in_constexpr() { |
1795 | 0 | #if FASTFLOAT_HAS_IS_CONSTANT_EVALUATED |
1796 | 0 | return std::is_constant_evaluated(); |
1797 | 0 | #else |
1798 | 0 | return false; |
1799 | 0 | #endif |
1800 | 0 | } |
1801 | | |
1802 | | template <typename T> |
1803 | | struct is_supported_float_type |
1804 | | : tinyobj_ff::integral_constant< |
1805 | | bool, tinyobj_ff::is_same<T, double>::value || tinyobj_ff::is_same<T, float>::value |
1806 | | #ifdef __STDCPP_FLOAT64_T__ |
1807 | | || tinyobj_ff::is_same<T, std::float64_t>::value |
1808 | | #endif |
1809 | | #ifdef __STDCPP_FLOAT32_T__ |
1810 | | || tinyobj_ff::is_same<T, std::float32_t>::value |
1811 | | #endif |
1812 | | #ifdef __STDCPP_FLOAT16_T__ |
1813 | | || tinyobj_ff::is_same<T, std::float16_t>::value |
1814 | | #endif |
1815 | | #ifdef __STDCPP_BFLOAT16_T__ |
1816 | | || tinyobj_ff::is_same<T, std::bfloat16_t>::value |
1817 | | #endif |
1818 | | > { |
1819 | | }; |
1820 | | |
1821 | | template <typename T> |
1822 | | using equiv_uint_t = typename tinyobj_ff::conditional< |
1823 | | sizeof(T) == 1, uint8_t, |
1824 | | typename tinyobj_ff::conditional< |
1825 | | sizeof(T) == 2, uint16_t, |
1826 | | typename tinyobj_ff::conditional<sizeof(T) == 4, uint32_t, |
1827 | | uint64_t>::type>::type>::type; |
1828 | | |
1829 | | template <typename T> struct is_supported_integer_type : tinyobj_ff::is_integral<T> {}; |
1830 | | |
1831 | | template <typename UC> |
1832 | | struct is_supported_char_type |
1833 | | : tinyobj_ff::integral_constant<bool, tinyobj_ff::is_same<UC, char>::value || |
1834 | | tinyobj_ff::is_same<UC, wchar_t>::value || |
1835 | | tinyobj_ff::is_same<UC, char16_t>::value || |
1836 | | tinyobj_ff::is_same<UC, char32_t>::value |
1837 | | #ifdef __cpp_char8_t |
1838 | | || tinyobj_ff::is_same<UC, char8_t>::value |
1839 | | #endif |
1840 | | > { |
1841 | | }; |
1842 | | |
1843 | | // Compares two ASCII strings in a case insensitive manner. |
1844 | | template <typename UC> |
1845 | | inline FASTFLOAT_CONSTEXPR14 bool |
1846 | | fastfloat_strncasecmp(UC const *actual_mixedcase, UC const *expected_lowercase, |
1847 | 130k | size_t length) { |
1848 | 156k | for (size_t i = 0; i < length; ++i) { |
1849 | 156k | UC const actual = actual_mixedcase[i]; |
1850 | 156k | if ((actual < 256 ? actual | 32 : actual) != expected_lowercase[i]) { |
1851 | 130k | return false; |
1852 | 130k | } |
1853 | 156k | } |
1854 | 0 | return true; |
1855 | 130k | } |
1856 | | |
1857 | | #ifndef FLT_EVAL_METHOD |
1858 | | #error "FLT_EVAL_METHOD should be defined, please include cfloat." |
1859 | | #endif |
1860 | | |
1861 | | // a pointer and a length to a contiguous block of memory |
1862 | | template <typename T> struct span { |
1863 | | T const *ptr; |
1864 | | size_t length; |
1865 | | |
1866 | 2.24M | constexpr span(T const *_ptr, size_t _length) : ptr(_ptr), length(_length) {}fast_float::span<char const>::span(char const*, unsigned long) Line | Count | Source | 1866 | 1.91M | constexpr span(T const *_ptr, size_t _length) : ptr(_ptr), length(_length) {} |
fast_float::span<unsigned long>::span(unsigned long const*, unsigned long) Line | Count | Source | 1866 | 327k | constexpr span(T const *_ptr, size_t _length) : ptr(_ptr), length(_length) {} |
|
1867 | | |
1868 | 4.03M | constexpr span() : ptr(nullptr), length(0) {} |
1869 | | |
1870 | 3.99M | constexpr size_t len() const noexcept { return length; }fast_float::span<char const>::len() const Line | Count | Source | 1870 | 161k | constexpr size_t len() const noexcept { return length; } |
fast_float::span<unsigned long>::len() const Line | Count | Source | 1870 | 3.83M | constexpr size_t len() const noexcept { return length; } |
|
1871 | | |
1872 | 2.42M | FASTFLOAT_CONSTEXPR14 const T &operator[](size_t index) const noexcept { |
1873 | 2.42M | FASTFLOAT_DEBUG_ASSERT(index < length); |
1874 | 2.42M | return ptr[index]; |
1875 | 2.42M | } |
1876 | | }; |
1877 | | |
1878 | | struct value128 { |
1879 | | uint64_t low; |
1880 | | uint64_t high; |
1881 | | |
1882 | 0 | constexpr value128(uint64_t _low, uint64_t _high) : low(_low), high(_high) {} |
1883 | | |
1884 | 209k | constexpr value128() : low(0), high(0) {} |
1885 | | }; |
1886 | | |
1887 | | /* Helper C++14 constexpr generic implementation of leading_zeroes */ |
1888 | | fastfloat_really_inline FASTFLOAT_CONSTEXPR14 int |
1889 | 0 | leading_zeroes_generic(uint64_t input_num, int last_bit = 0) { |
1890 | 0 | if (input_num & uint64_t(0xffffffff00000000)) { |
1891 | 0 | input_num >>= 32; |
1892 | 0 | last_bit |= 32; |
1893 | 0 | } |
1894 | 0 | if (input_num & uint64_t(0xffff0000)) { |
1895 | 0 | input_num >>= 16; |
1896 | 0 | last_bit |= 16; |
1897 | 0 | } |
1898 | 0 | if (input_num & uint64_t(0xff00)) { |
1899 | 0 | input_num >>= 8; |
1900 | 0 | last_bit |= 8; |
1901 | 0 | } |
1902 | 0 | if (input_num & uint64_t(0xf0)) { |
1903 | 0 | input_num >>= 4; |
1904 | 0 | last_bit |= 4; |
1905 | 0 | } |
1906 | 0 | if (input_num & uint64_t(0xc)) { |
1907 | 0 | input_num >>= 2; |
1908 | 0 | last_bit |= 2; |
1909 | 0 | } |
1910 | 0 | if (input_num & uint64_t(0x2)) { /* input_num >>= 1; */ |
1911 | 0 | last_bit |= 1; |
1912 | 0 | } |
1913 | 0 | return 63 - last_bit; |
1914 | 0 | } |
1915 | | |
1916 | | /* result might be undefined when input_num is zero */ |
1917 | | fastfloat_really_inline FASTFLOAT_CONSTEXPR20 int |
1918 | 196k | leading_zeroes(uint64_t input_num) { |
1919 | 196k | assert(input_num > 0); |
1920 | 196k | if (cpp20_and_in_constexpr()) { |
1921 | 0 | return leading_zeroes_generic(input_num); |
1922 | 0 | } |
1923 | | #ifdef FASTFLOAT_VISUAL_STUDIO |
1924 | | #if defined(_M_X64) || defined(_M_ARM64) |
1925 | | unsigned long leading_zero = 0; |
1926 | | // Search the mask data from most significant bit (MSB) |
1927 | | // to least significant bit (LSB) for a set bit (1). |
1928 | | _BitScanReverse64(&leading_zero, input_num); |
1929 | | return (int)(63 - leading_zero); |
1930 | | #else |
1931 | | return leading_zeroes_generic(input_num); |
1932 | | #endif |
1933 | | #else |
1934 | 196k | return __builtin_clzll(input_num); |
1935 | 196k | #endif |
1936 | 196k | } |
1937 | | |
1938 | | // slow emulation routine for 32-bit |
1939 | 0 | fastfloat_really_inline constexpr uint64_t emulu(uint32_t x, uint32_t y) { |
1940 | 0 | return x * (uint64_t)y; |
1941 | 0 | } |
1942 | | |
1943 | | fastfloat_really_inline FASTFLOAT_CONSTEXPR14 uint64_t |
1944 | 0 | umul128_generic(uint64_t ab, uint64_t cd, uint64_t *hi) { |
1945 | 0 | uint64_t ad = emulu((uint32_t)(ab >> 32), (uint32_t)cd); |
1946 | 0 | uint64_t bd = emulu((uint32_t)ab, (uint32_t)cd); |
1947 | 0 | uint64_t adbc = ad + emulu((uint32_t)ab, (uint32_t)(cd >> 32)); |
1948 | 0 | uint64_t adbc_carry = (uint64_t)(adbc < ad); |
1949 | 0 | uint64_t lo = bd + (adbc << 32); |
1950 | 0 | *hi = emulu((uint32_t)(ab >> 32), (uint32_t)(cd >> 32)) + (adbc >> 32) + |
1951 | 0 | (adbc_carry << 32) + (uint64_t)(lo < bd); |
1952 | 0 | return lo; |
1953 | 0 | } |
1954 | | |
1955 | | #ifdef FASTFLOAT_32BIT |
1956 | | |
1957 | | // slow emulation routine for 32-bit |
1958 | | #if !defined(__MINGW64__) |
1959 | | fastfloat_really_inline FASTFLOAT_CONSTEXPR14 uint64_t _umul128(uint64_t ab, |
1960 | | uint64_t cd, |
1961 | | uint64_t *hi) { |
1962 | | return umul128_generic(ab, cd, hi); |
1963 | | } |
1964 | | #endif // !__MINGW64__ |
1965 | | |
1966 | | #endif // FASTFLOAT_32BIT |
1967 | | |
1968 | | // compute 64-bit a*b |
1969 | | fastfloat_really_inline FASTFLOAT_CONSTEXPR20 value128 |
1970 | 209k | full_multiplication(uint64_t a, uint64_t b) { |
1971 | 209k | if (cpp20_and_in_constexpr()) { |
1972 | 0 | value128 answer; |
1973 | 0 | answer.low = umul128_generic(a, b, &answer.high); |
1974 | 0 | return answer; |
1975 | 0 | } |
1976 | 209k | value128 answer; |
1977 | | #if defined(_M_ARM64) && !defined(__MINGW32__) |
1978 | | // ARM64 has native support for 64-bit multiplications, no need to emulate |
1979 | | // But MinGW on ARM64 doesn't have native support for 64-bit multiplications |
1980 | | answer.high = __umulh(a, b); |
1981 | | answer.low = a * b; |
1982 | | #elif defined(FASTFLOAT_32BIT) || \ |
1983 | | (defined(_WIN64) && !defined(__clang__) && !defined(_M_ARM64)) |
1984 | | answer.low = _umul128(a, b, &answer.high); // _umul128 not available on ARM64 |
1985 | | #elif defined(FASTFLOAT_64BIT) && defined(__SIZEOF_INT128__) |
1986 | | __uint128_t r = ((__uint128_t)a) * b; |
1987 | 209k | answer.low = uint64_t(r); |
1988 | 209k | answer.high = uint64_t(r >> 64); |
1989 | | #else |
1990 | | answer.low = umul128_generic(a, b, &answer.high); |
1991 | | #endif |
1992 | 209k | return answer; |
1993 | 209k | } |
1994 | | |
1995 | | struct adjusted_mantissa { |
1996 | | uint64_t mantissa{0}; |
1997 | | int32_t power2{0}; // a negative value indicates an invalid result |
1998 | 213k | adjusted_mantissa() = default; |
1999 | | |
2000 | 0 | constexpr bool operator==(adjusted_mantissa const &o) const { |
2001 | 0 | return mantissa == o.mantissa && power2 == o.power2; |
2002 | 0 | } |
2003 | | |
2004 | 68.9k | constexpr bool operator!=(adjusted_mantissa const &o) const { |
2005 | 68.9k | return mantissa != o.mantissa || power2 != o.power2; |
2006 | 68.9k | } |
2007 | | }; |
2008 | | |
2009 | | // Bias so we can get the real exponent with an invalid adjusted_mantissa. |
2010 | | constexpr static int32_t invalid_am_bias = -0x8000; |
2011 | | |
2012 | | // used for binary_format_lookup_tables<T>::max_mantissa |
2013 | | constexpr uint64_t constant_55555 = 5 * 5 * 5 * 5 * 5; |
2014 | | |
2015 | | template <typename T, typename U = void> struct binary_format_lookup_tables; |
2016 | | |
2017 | | template <typename T> struct binary_format : binary_format_lookup_tables<T> { |
2018 | | using equiv_uint = equiv_uint_t<T>; |
2019 | | |
2020 | | static constexpr int mantissa_explicit_bits(); |
2021 | | static constexpr int minimum_exponent(); |
2022 | | static constexpr int infinite_power(); |
2023 | | static constexpr int sign_index(); |
2024 | | static constexpr int |
2025 | | min_exponent_fast_path(); // used when fegetround() == FE_TONEAREST |
2026 | | static constexpr int max_exponent_fast_path(); |
2027 | | static constexpr int max_exponent_round_to_even(); |
2028 | | static constexpr int min_exponent_round_to_even(); |
2029 | | static constexpr uint64_t max_mantissa_fast_path(int64_t power); |
2030 | | static constexpr uint64_t |
2031 | | max_mantissa_fast_path(); // used when fegetround() == FE_TONEAREST |
2032 | | static constexpr int largest_power_of_ten(); |
2033 | | static constexpr int smallest_power_of_ten(); |
2034 | | static constexpr T exact_power_of_ten(int64_t power); |
2035 | | static constexpr size_t max_digits(); |
2036 | | static constexpr equiv_uint exponent_mask(); |
2037 | | static constexpr equiv_uint mantissa_mask(); |
2038 | | static constexpr equiv_uint hidden_bit_mask(); |
2039 | | }; |
2040 | | |
2041 | | template <typename U> struct binary_format_lookup_tables<double, U> { |
2042 | | static constexpr double powers_of_ten[] = { |
2043 | | 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, |
2044 | | 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22}; |
2045 | | |
2046 | | // Largest integer value v so that (5**index * v) <= 1<<53. |
2047 | | // 0x20000000000000 == 1 << 53 |
2048 | | static constexpr uint64_t max_mantissa[] = { |
2049 | | 0x20000000000000, |
2050 | | 0x20000000000000 / 5, |
2051 | | 0x20000000000000 / (5 * 5), |
2052 | | 0x20000000000000 / (5 * 5 * 5), |
2053 | | 0x20000000000000 / (5 * 5 * 5 * 5), |
2054 | | 0x20000000000000 / (constant_55555), |
2055 | | 0x20000000000000 / (constant_55555 * 5), |
2056 | | 0x20000000000000 / (constant_55555 * 5 * 5), |
2057 | | 0x20000000000000 / (constant_55555 * 5 * 5 * 5), |
2058 | | 0x20000000000000 / (constant_55555 * 5 * 5 * 5 * 5), |
2059 | | 0x20000000000000 / (constant_55555 * constant_55555), |
2060 | | 0x20000000000000 / (constant_55555 * constant_55555 * 5), |
2061 | | 0x20000000000000 / (constant_55555 * constant_55555 * 5 * 5), |
2062 | | 0x20000000000000 / (constant_55555 * constant_55555 * 5 * 5 * 5), |
2063 | | 0x20000000000000 / (constant_55555 * constant_55555 * constant_55555), |
2064 | | 0x20000000000000 / (constant_55555 * constant_55555 * constant_55555 * 5), |
2065 | | 0x20000000000000 / |
2066 | | (constant_55555 * constant_55555 * constant_55555 * 5 * 5), |
2067 | | 0x20000000000000 / |
2068 | | (constant_55555 * constant_55555 * constant_55555 * 5 * 5 * 5), |
2069 | | 0x20000000000000 / |
2070 | | (constant_55555 * constant_55555 * constant_55555 * 5 * 5 * 5 * 5), |
2071 | | 0x20000000000000 / |
2072 | | (constant_55555 * constant_55555 * constant_55555 * constant_55555), |
2073 | | 0x20000000000000 / (constant_55555 * constant_55555 * constant_55555 * |
2074 | | constant_55555 * 5), |
2075 | | 0x20000000000000 / (constant_55555 * constant_55555 * constant_55555 * |
2076 | | constant_55555 * 5 * 5), |
2077 | | 0x20000000000000 / (constant_55555 * constant_55555 * constant_55555 * |
2078 | | constant_55555 * 5 * 5 * 5), |
2079 | | 0x20000000000000 / (constant_55555 * constant_55555 * constant_55555 * |
2080 | | constant_55555 * 5 * 5 * 5 * 5)}; |
2081 | | }; |
2082 | | |
2083 | | #if FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE |
2084 | | |
2085 | | template <typename U> |
2086 | | constexpr double binary_format_lookup_tables<double, U>::powers_of_ten[]; |
2087 | | |
2088 | | template <typename U> |
2089 | | constexpr uint64_t binary_format_lookup_tables<double, U>::max_mantissa[]; |
2090 | | |
2091 | | #endif |
2092 | | |
2093 | | template <typename U> struct binary_format_lookup_tables<float, U> { |
2094 | | static constexpr float powers_of_ten[] = {1e0f, 1e1f, 1e2f, 1e3f, 1e4f, 1e5f, |
2095 | | 1e6f, 1e7f, 1e8f, 1e9f, 1e10f}; |
2096 | | |
2097 | | // Largest integer value v so that (5**index * v) <= 1<<24. |
2098 | | // 0x1000000 == 1<<24 |
2099 | | static constexpr uint64_t max_mantissa[] = { |
2100 | | 0x1000000, |
2101 | | 0x1000000 / 5, |
2102 | | 0x1000000 / (5 * 5), |
2103 | | 0x1000000 / (5 * 5 * 5), |
2104 | | 0x1000000 / (5 * 5 * 5 * 5), |
2105 | | 0x1000000 / (constant_55555), |
2106 | | 0x1000000 / (constant_55555 * 5), |
2107 | | 0x1000000 / (constant_55555 * 5 * 5), |
2108 | | 0x1000000 / (constant_55555 * 5 * 5 * 5), |
2109 | | 0x1000000 / (constant_55555 * 5 * 5 * 5 * 5), |
2110 | | 0x1000000 / (constant_55555 * constant_55555), |
2111 | | 0x1000000 / (constant_55555 * constant_55555 * 5)}; |
2112 | | }; |
2113 | | |
2114 | | #if FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE |
2115 | | |
2116 | | template <typename U> |
2117 | | constexpr float binary_format_lookup_tables<float, U>::powers_of_ten[]; |
2118 | | |
2119 | | template <typename U> |
2120 | | constexpr uint64_t binary_format_lookup_tables<float, U>::max_mantissa[]; |
2121 | | |
2122 | | #endif |
2123 | | |
2124 | | template <> |
2125 | 1.73M | inline constexpr int binary_format<double>::min_exponent_fast_path() { |
2126 | | #if (FLT_EVAL_METHOD != 1) && (FLT_EVAL_METHOD != 0) |
2127 | | return 0; |
2128 | | #else |
2129 | 1.73M | return -22; |
2130 | 1.73M | #endif |
2131 | 1.73M | } |
2132 | | |
2133 | | template <> |
2134 | 0 | inline constexpr int binary_format<float>::min_exponent_fast_path() { |
2135 | | #if (FLT_EVAL_METHOD != 1) && (FLT_EVAL_METHOD != 0) |
2136 | | return 0; |
2137 | | #else |
2138 | 0 | return -10; |
2139 | 0 | #endif |
2140 | 0 | } |
2141 | | |
2142 | | template <> |
2143 | 2.42M | inline constexpr int binary_format<double>::mantissa_explicit_bits() { |
2144 | 2.42M | return 52; |
2145 | 2.42M | } |
2146 | | |
2147 | | template <> |
2148 | 0 | inline constexpr int binary_format<float>::mantissa_explicit_bits() { |
2149 | 0 | return 23; |
2150 | 0 | } |
2151 | | |
2152 | | template <> |
2153 | 17.5k | inline constexpr int binary_format<double>::max_exponent_round_to_even() { |
2154 | 17.5k | return 23; |
2155 | 17.5k | } |
2156 | | |
2157 | | template <> |
2158 | 0 | inline constexpr int binary_format<float>::max_exponent_round_to_even() { |
2159 | 0 | return 10; |
2160 | 0 | } |
2161 | | |
2162 | | template <> |
2163 | 25.0k | inline constexpr int binary_format<double>::min_exponent_round_to_even() { |
2164 | 25.0k | return -4; |
2165 | 25.0k | } |
2166 | | |
2167 | | template <> |
2168 | 0 | inline constexpr int binary_format<float>::min_exponent_round_to_even() { |
2169 | 0 | return -17; |
2170 | 0 | } |
2171 | | |
2172 | 206k | template <> inline constexpr int binary_format<double>::minimum_exponent() { |
2173 | 206k | return -1023; |
2174 | 206k | } |
2175 | | |
2176 | 0 | template <> inline constexpr int binary_format<float>::minimum_exponent() { |
2177 | 0 | return -127; |
2178 | 0 | } |
2179 | | |
2180 | 263k | template <> inline constexpr int binary_format<double>::infinite_power() { |
2181 | 263k | return 0x7FF; |
2182 | 263k | } |
2183 | | |
2184 | 0 | template <> inline constexpr int binary_format<float>::infinite_power() { |
2185 | 0 | return 0xFF; |
2186 | 0 | } |
2187 | | |
2188 | 97.3k | template <> inline constexpr int binary_format<double>::sign_index() { |
2189 | 97.3k | return 63; |
2190 | 97.3k | } |
2191 | | |
2192 | 0 | template <> inline constexpr int binary_format<float>::sign_index() { |
2193 | 0 | return 31; |
2194 | 0 | } |
2195 | | |
2196 | | template <> |
2197 | 1.71M | inline constexpr int binary_format<double>::max_exponent_fast_path() { |
2198 | 1.71M | return 22; |
2199 | 1.71M | } |
2200 | | |
2201 | | template <> |
2202 | 0 | inline constexpr int binary_format<float>::max_exponent_fast_path() { |
2203 | 0 | return 10; |
2204 | 0 | } |
2205 | | |
2206 | | template <> |
2207 | 1.66M | inline constexpr uint64_t binary_format<double>::max_mantissa_fast_path() { |
2208 | 1.66M | return uint64_t(2) << mantissa_explicit_bits(); |
2209 | 1.66M | } |
2210 | | |
2211 | | template <> |
2212 | 0 | inline constexpr uint64_t binary_format<float>::max_mantissa_fast_path() { |
2213 | 0 | return uint64_t(2) << mantissa_explicit_bits(); |
2214 | 0 | } |
2215 | | |
2216 | | // credit: Jakub JelÃnek |
2217 | | #ifdef __STDCPP_FLOAT16_T__ |
2218 | | template <typename U> struct binary_format_lookup_tables<std::float16_t, U> { |
2219 | | static constexpr std::float16_t powers_of_ten[] = {1e0f16, 1e1f16, 1e2f16, |
2220 | | 1e3f16, 1e4f16}; |
2221 | | |
2222 | | // Largest integer value v so that (5**index * v) <= 1<<11. |
2223 | | // 0x800 == 1<<11 |
2224 | | static constexpr uint64_t max_mantissa[] = {0x800, |
2225 | | 0x800 / 5, |
2226 | | 0x800 / (5 * 5), |
2227 | | 0x800 / (5 * 5 * 5), |
2228 | | 0x800 / (5 * 5 * 5 * 5), |
2229 | | 0x800 / (constant_55555)}; |
2230 | | }; |
2231 | | |
2232 | | #if FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE |
2233 | | |
2234 | | template <typename U> |
2235 | | constexpr std::float16_t |
2236 | | binary_format_lookup_tables<std::float16_t, U>::powers_of_ten[]; |
2237 | | |
2238 | | template <typename U> |
2239 | | constexpr uint64_t |
2240 | | binary_format_lookup_tables<std::float16_t, U>::max_mantissa[]; |
2241 | | |
2242 | | #endif |
2243 | | |
2244 | | template <> |
2245 | | inline constexpr std::float16_t |
2246 | | binary_format<std::float16_t>::exact_power_of_ten(int64_t power) { |
2247 | | // Work around clang bug https://godbolt.org/z/zedh7rrhc |
2248 | | return (void)powers_of_ten[0], powers_of_ten[power]; |
2249 | | } |
2250 | | |
2251 | | template <> |
2252 | | inline constexpr binary_format<std::float16_t>::equiv_uint |
2253 | | binary_format<std::float16_t>::exponent_mask() { |
2254 | | return 0x7C00; |
2255 | | } |
2256 | | |
2257 | | template <> |
2258 | | inline constexpr binary_format<std::float16_t>::equiv_uint |
2259 | | binary_format<std::float16_t>::mantissa_mask() { |
2260 | | return 0x03FF; |
2261 | | } |
2262 | | |
2263 | | template <> |
2264 | | inline constexpr binary_format<std::float16_t>::equiv_uint |
2265 | | binary_format<std::float16_t>::hidden_bit_mask() { |
2266 | | return 0x0400; |
2267 | | } |
2268 | | |
2269 | | template <> |
2270 | | inline constexpr int binary_format<std::float16_t>::max_exponent_fast_path() { |
2271 | | return 4; |
2272 | | } |
2273 | | |
2274 | | template <> |
2275 | | inline constexpr int binary_format<std::float16_t>::mantissa_explicit_bits() { |
2276 | | return 10; |
2277 | | } |
2278 | | |
2279 | | template <> |
2280 | | inline constexpr uint64_t |
2281 | | binary_format<std::float16_t>::max_mantissa_fast_path() { |
2282 | | return uint64_t(2) << mantissa_explicit_bits(); |
2283 | | } |
2284 | | |
2285 | | template <> |
2286 | | inline constexpr uint64_t |
2287 | | binary_format<std::float16_t>::max_mantissa_fast_path(int64_t power) { |
2288 | | // caller is responsible to ensure that |
2289 | | // power >= 0 && power <= 4 |
2290 | | // |
2291 | | // Work around clang bug https://godbolt.org/z/zedh7rrhc |
2292 | | return (void)max_mantissa[0], max_mantissa[power]; |
2293 | | } |
2294 | | |
2295 | | template <> |
2296 | | inline constexpr int binary_format<std::float16_t>::min_exponent_fast_path() { |
2297 | | return 0; |
2298 | | } |
2299 | | |
2300 | | template <> |
2301 | | inline constexpr int |
2302 | | binary_format<std::float16_t>::max_exponent_round_to_even() { |
2303 | | return 5; |
2304 | | } |
2305 | | |
2306 | | template <> |
2307 | | inline constexpr int |
2308 | | binary_format<std::float16_t>::min_exponent_round_to_even() { |
2309 | | return -22; |
2310 | | } |
2311 | | |
2312 | | template <> |
2313 | | inline constexpr int binary_format<std::float16_t>::minimum_exponent() { |
2314 | | return -15; |
2315 | | } |
2316 | | |
2317 | | template <> |
2318 | | inline constexpr int binary_format<std::float16_t>::infinite_power() { |
2319 | | return 0x1F; |
2320 | | } |
2321 | | |
2322 | | template <> inline constexpr int binary_format<std::float16_t>::sign_index() { |
2323 | | return 15; |
2324 | | } |
2325 | | |
2326 | | template <> |
2327 | | inline constexpr int binary_format<std::float16_t>::largest_power_of_ten() { |
2328 | | return 4; |
2329 | | } |
2330 | | |
2331 | | template <> |
2332 | | inline constexpr int binary_format<std::float16_t>::smallest_power_of_ten() { |
2333 | | return -27; |
2334 | | } |
2335 | | |
2336 | | template <> |
2337 | | inline constexpr size_t binary_format<std::float16_t>::max_digits() { |
2338 | | return 22; |
2339 | | } |
2340 | | #endif // __STDCPP_FLOAT16_T__ |
2341 | | |
2342 | | // credit: Jakub JelÃnek |
2343 | | #ifdef __STDCPP_BFLOAT16_T__ |
2344 | | template <typename U> struct binary_format_lookup_tables<std::bfloat16_t, U> { |
2345 | | static constexpr std::bfloat16_t powers_of_ten[] = {1e0bf16, 1e1bf16, 1e2bf16, |
2346 | | 1e3bf16}; |
2347 | | |
2348 | | // Largest integer value v so that (5**index * v) <= 1<<8. |
2349 | | // 0x100 == 1<<8 |
2350 | | static constexpr uint64_t max_mantissa[] = {0x100, 0x100 / 5, 0x100 / (5 * 5), |
2351 | | 0x100 / (5 * 5 * 5), |
2352 | | 0x100 / (5 * 5 * 5 * 5)}; |
2353 | | }; |
2354 | | |
2355 | | #if FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE |
2356 | | |
2357 | | template <typename U> |
2358 | | constexpr std::bfloat16_t |
2359 | | binary_format_lookup_tables<std::bfloat16_t, U>::powers_of_ten[]; |
2360 | | |
2361 | | template <typename U> |
2362 | | constexpr uint64_t |
2363 | | binary_format_lookup_tables<std::bfloat16_t, U>::max_mantissa[]; |
2364 | | |
2365 | | #endif |
2366 | | |
2367 | | template <> |
2368 | | inline constexpr std::bfloat16_t |
2369 | | binary_format<std::bfloat16_t>::exact_power_of_ten(int64_t power) { |
2370 | | // Work around clang bug https://godbolt.org/z/zedh7rrhc |
2371 | | return (void)powers_of_ten[0], powers_of_ten[power]; |
2372 | | } |
2373 | | |
2374 | | template <> |
2375 | | inline constexpr int binary_format<std::bfloat16_t>::max_exponent_fast_path() { |
2376 | | return 3; |
2377 | | } |
2378 | | |
2379 | | template <> |
2380 | | inline constexpr binary_format<std::bfloat16_t>::equiv_uint |
2381 | | binary_format<std::bfloat16_t>::exponent_mask() { |
2382 | | return 0x7F80; |
2383 | | } |
2384 | | |
2385 | | template <> |
2386 | | inline constexpr binary_format<std::bfloat16_t>::equiv_uint |
2387 | | binary_format<std::bfloat16_t>::mantissa_mask() { |
2388 | | return 0x007F; |
2389 | | } |
2390 | | |
2391 | | template <> |
2392 | | inline constexpr binary_format<std::bfloat16_t>::equiv_uint |
2393 | | binary_format<std::bfloat16_t>::hidden_bit_mask() { |
2394 | | return 0x0080; |
2395 | | } |
2396 | | |
2397 | | template <> |
2398 | | inline constexpr int binary_format<std::bfloat16_t>::mantissa_explicit_bits() { |
2399 | | return 7; |
2400 | | } |
2401 | | |
2402 | | template <> |
2403 | | inline constexpr uint64_t |
2404 | | binary_format<std::bfloat16_t>::max_mantissa_fast_path() { |
2405 | | return uint64_t(2) << mantissa_explicit_bits(); |
2406 | | } |
2407 | | |
2408 | | template <> |
2409 | | inline constexpr uint64_t |
2410 | | binary_format<std::bfloat16_t>::max_mantissa_fast_path(int64_t power) { |
2411 | | // caller is responsible to ensure that |
2412 | | // power >= 0 && power <= 3 |
2413 | | // |
2414 | | // Work around clang bug https://godbolt.org/z/zedh7rrhc |
2415 | | return (void)max_mantissa[0], max_mantissa[power]; |
2416 | | } |
2417 | | |
2418 | | template <> |
2419 | | inline constexpr int binary_format<std::bfloat16_t>::min_exponent_fast_path() { |
2420 | | return 0; |
2421 | | } |
2422 | | |
2423 | | template <> |
2424 | | inline constexpr int |
2425 | | binary_format<std::bfloat16_t>::max_exponent_round_to_even() { |
2426 | | return 3; |
2427 | | } |
2428 | | |
2429 | | template <> |
2430 | | inline constexpr int |
2431 | | binary_format<std::bfloat16_t>::min_exponent_round_to_even() { |
2432 | | return -24; |
2433 | | } |
2434 | | |
2435 | | template <> |
2436 | | inline constexpr int binary_format<std::bfloat16_t>::minimum_exponent() { |
2437 | | return -127; |
2438 | | } |
2439 | | |
2440 | | template <> |
2441 | | inline constexpr int binary_format<std::bfloat16_t>::infinite_power() { |
2442 | | return 0xFF; |
2443 | | } |
2444 | | |
2445 | | template <> inline constexpr int binary_format<std::bfloat16_t>::sign_index() { |
2446 | | return 15; |
2447 | | } |
2448 | | |
2449 | | template <> |
2450 | | inline constexpr int binary_format<std::bfloat16_t>::largest_power_of_ten() { |
2451 | | return 38; |
2452 | | } |
2453 | | |
2454 | | template <> |
2455 | | inline constexpr int binary_format<std::bfloat16_t>::smallest_power_of_ten() { |
2456 | | return -60; |
2457 | | } |
2458 | | |
2459 | | template <> |
2460 | | inline constexpr size_t binary_format<std::bfloat16_t>::max_digits() { |
2461 | | return 98; |
2462 | | } |
2463 | | #endif // __STDCPP_BFLOAT16_T__ |
2464 | | |
2465 | | template <> |
2466 | | inline constexpr uint64_t |
2467 | 0 | binary_format<double>::max_mantissa_fast_path(int64_t power) { |
2468 | | // caller is responsible to ensure that |
2469 | | // power >= 0 && power <= 22 |
2470 | | // |
2471 | | // Work around clang bug https://godbolt.org/z/zedh7rrhc |
2472 | 0 | return (void)max_mantissa[0], max_mantissa[power]; |
2473 | 0 | } |
2474 | | |
2475 | | template <> |
2476 | | inline constexpr uint64_t |
2477 | 0 | binary_format<float>::max_mantissa_fast_path(int64_t power) { |
2478 | | // caller is responsible to ensure that |
2479 | | // power >= 0 && power <= 10 |
2480 | | // |
2481 | | // Work around clang bug https://godbolt.org/z/zedh7rrhc |
2482 | 0 | return (void)max_mantissa[0], max_mantissa[power]; |
2483 | 0 | } |
2484 | | |
2485 | | template <> |
2486 | | inline constexpr double |
2487 | 1.65M | binary_format<double>::exact_power_of_ten(int64_t power) { |
2488 | | // Work around clang bug https://godbolt.org/z/zedh7rrhc |
2489 | 1.65M | return (void)powers_of_ten[0], powers_of_ten[power]; |
2490 | 1.65M | } |
2491 | | |
2492 | | template <> |
2493 | 0 | inline constexpr float binary_format<float>::exact_power_of_ten(int64_t power) { |
2494 | | // Work around clang bug https://godbolt.org/z/zedh7rrhc |
2495 | 0 | return (void)powers_of_ten[0], powers_of_ten[power]; |
2496 | 0 | } |
2497 | | |
2498 | 141k | template <> inline constexpr int binary_format<double>::largest_power_of_ten() { |
2499 | 141k | return 308; |
2500 | 141k | } |
2501 | | |
2502 | 0 | template <> inline constexpr int binary_format<float>::largest_power_of_ten() { |
2503 | 0 | return 38; |
2504 | 0 | } |
2505 | | |
2506 | | template <> |
2507 | 143k | inline constexpr int binary_format<double>::smallest_power_of_ten() { |
2508 | 143k | return -342; |
2509 | 143k | } |
2510 | | |
2511 | 0 | template <> inline constexpr int binary_format<float>::smallest_power_of_ten() { |
2512 | 0 | return -64; |
2513 | 0 | } |
2514 | | |
2515 | 35.1k | template <> inline constexpr size_t binary_format<double>::max_digits() { |
2516 | 35.1k | return 769; |
2517 | 35.1k | } |
2518 | | |
2519 | 0 | template <> inline constexpr size_t binary_format<float>::max_digits() { |
2520 | 0 | return 114; |
2521 | 0 | } |
2522 | | |
2523 | | template <> |
2524 | | inline constexpr binary_format<float>::equiv_uint |
2525 | 0 | binary_format<float>::exponent_mask() { |
2526 | 0 | return 0x7F800000; |
2527 | 0 | } |
2528 | | |
2529 | | template <> |
2530 | | inline constexpr binary_format<double>::equiv_uint |
2531 | 0 | binary_format<double>::exponent_mask() { |
2532 | 0 | return 0x7FF0000000000000; |
2533 | 0 | } |
2534 | | |
2535 | | template <> |
2536 | | inline constexpr binary_format<float>::equiv_uint |
2537 | 0 | binary_format<float>::mantissa_mask() { |
2538 | 0 | return 0x007FFFFF; |
2539 | 0 | } |
2540 | | |
2541 | | template <> |
2542 | | inline constexpr binary_format<double>::equiv_uint |
2543 | 0 | binary_format<double>::mantissa_mask() { |
2544 | 0 | return 0x000FFFFFFFFFFFFF; |
2545 | 0 | } |
2546 | | |
2547 | | template <> |
2548 | | inline constexpr binary_format<float>::equiv_uint |
2549 | 0 | binary_format<float>::hidden_bit_mask() { |
2550 | 0 | return 0x00800000; |
2551 | 0 | } |
2552 | | |
2553 | | template <> |
2554 | | inline constexpr binary_format<double>::equiv_uint |
2555 | 0 | binary_format<double>::hidden_bit_mask() { |
2556 | 0 | return 0x0010000000000000; |
2557 | 0 | } |
2558 | | |
2559 | | template <typename T> |
2560 | | fastfloat_really_inline FASTFLOAT_CONSTEXPR20 void |
2561 | 97.3k | to_float(bool negative, adjusted_mantissa am, T &value) { |
2562 | 97.3k | using equiv_uint = equiv_uint_t<T>; |
2563 | 97.3k | equiv_uint word = equiv_uint(am.mantissa); |
2564 | 97.3k | word = equiv_uint(word | equiv_uint(am.power2) |
2565 | 97.3k | << binary_format<T>::mantissa_explicit_bits()); |
2566 | 97.3k | word = |
2567 | 97.3k | equiv_uint(word | equiv_uint(negative) << binary_format<T>::sign_index()); |
2568 | | #if FASTFLOAT_HAS_BIT_CAST |
2569 | | value = std::bit_cast<T>(word); |
2570 | | #else |
2571 | 97.3k | ::memcpy(&value, &word, sizeof(T)); |
2572 | 97.3k | #endif |
2573 | 97.3k | } void fast_float::to_float<double>(bool, fast_float::adjusted_mantissa, double&) Line | Count | Source | 2561 | 97.3k | to_float(bool negative, adjusted_mantissa am, T &value) { | 2562 | 97.3k | using equiv_uint = equiv_uint_t<T>; | 2563 | 97.3k | equiv_uint word = equiv_uint(am.mantissa); | 2564 | 97.3k | word = equiv_uint(word | equiv_uint(am.power2) | 2565 | 97.3k | << binary_format<T>::mantissa_explicit_bits()); | 2566 | 97.3k | word = | 2567 | 97.3k | equiv_uint(word | equiv_uint(negative) << binary_format<T>::sign_index()); | 2568 | | #if FASTFLOAT_HAS_BIT_CAST | 2569 | | value = std::bit_cast<T>(word); | 2570 | | #else | 2571 | 97.3k | ::memcpy(&value, &word, sizeof(T)); | 2572 | 97.3k | #endif | 2573 | 97.3k | } |
Unexecuted instantiation: void fast_float::to_float<float>(bool, fast_float::adjusted_mantissa, float&) |
2574 | | |
2575 | | template <typename = void> struct space_lut { |
2576 | | static constexpr bool value[] = { |
2577 | | 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
2578 | | 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
2579 | | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
2580 | | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
2581 | | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
2582 | | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
2583 | | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
2584 | | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
2585 | | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
2586 | | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
2587 | | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; |
2588 | | }; |
2589 | | |
2590 | | #if FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE |
2591 | | |
2592 | | template <typename T> constexpr bool space_lut<T>::value[]; |
2593 | | |
2594 | | #endif |
2595 | | |
2596 | 0 | template <typename UC> constexpr bool is_space(UC c) { |
2597 | 0 | return c < 256 && space_lut<>::value[uint8_t(c)]; |
2598 | 0 | } |
2599 | | |
2600 | 224k | template <typename UC> static constexpr uint64_t int_cmp_zeros() { |
2601 | 224k | static_assert((sizeof(UC) == 1) || (sizeof(UC) == 2) || (sizeof(UC) == 4), |
2602 | 224k | "Unsupported character size"); |
2603 | 224k | return (sizeof(UC) == 1) ? 0x3030303030303030 |
2604 | 224k | : (sizeof(UC) == 2) |
2605 | 0 | ? (uint64_t(UC('0')) << 48 | uint64_t(UC('0')) << 32 | |
2606 | 0 | uint64_t(UC('0')) << 16 | UC('0')) |
2607 | 0 | : (uint64_t(UC('0')) << 32 | UC('0')); |
2608 | 224k | } |
2609 | | |
2610 | 436k | template <typename UC> static constexpr int int_cmp_len() { |
2611 | 436k | return sizeof(uint64_t) / sizeof(UC); |
2612 | 436k | } |
2613 | | |
2614 | | template <typename UC> constexpr UC const *str_const_nan(); |
2615 | | |
2616 | 65.1k | template <> constexpr char const *str_const_nan<char>() { return "nan"; } |
2617 | | |
2618 | 0 | template <> constexpr wchar_t const *str_const_nan<wchar_t>() { return L"nan"; } |
2619 | | |
2620 | 0 | template <> constexpr char16_t const *str_const_nan<char16_t>() { |
2621 | 0 | return u"nan"; |
2622 | 0 | } |
2623 | | |
2624 | 0 | template <> constexpr char32_t const *str_const_nan<char32_t>() { |
2625 | 0 | return U"nan"; |
2626 | 0 | } |
2627 | | |
2628 | | #ifdef __cpp_char8_t |
2629 | | template <> constexpr char8_t const *str_const_nan<char8_t>() { |
2630 | | return u8"nan"; |
2631 | | } |
2632 | | #endif |
2633 | | |
2634 | | template <typename UC> constexpr UC const *str_const_inf(); |
2635 | | |
2636 | 65.1k | template <> constexpr char const *str_const_inf<char>() { return "infinity"; } |
2637 | | |
2638 | 0 | template <> constexpr wchar_t const *str_const_inf<wchar_t>() { |
2639 | 0 | return L"infinity"; |
2640 | 0 | } |
2641 | | |
2642 | 0 | template <> constexpr char16_t const *str_const_inf<char16_t>() { |
2643 | 0 | return u"infinity"; |
2644 | 0 | } |
2645 | | |
2646 | 0 | template <> constexpr char32_t const *str_const_inf<char32_t>() { |
2647 | 0 | return U"infinity"; |
2648 | 0 | } |
2649 | | |
2650 | | #ifdef __cpp_char8_t |
2651 | | template <> constexpr char8_t const *str_const_inf<char8_t>() { |
2652 | | return u8"infinity"; |
2653 | | } |
2654 | | #endif |
2655 | | |
2656 | | template <typename = void> struct int_luts { |
2657 | | static constexpr uint8_t chdigit[] = { |
2658 | | 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, |
2659 | | 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, |
2660 | | 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, |
2661 | | 255, 255, 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 255, 255, |
2662 | | 255, 255, 255, 255, 255, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, |
2663 | | 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, |
2664 | | 35, 255, 255, 255, 255, 255, 255, 10, 11, 12, 13, 14, 15, 16, 17, |
2665 | | 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, |
2666 | | 33, 34, 35, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, |
2667 | | 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, |
2668 | | 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, |
2669 | | 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, |
2670 | | 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, |
2671 | | 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, |
2672 | | 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, |
2673 | | 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, |
2674 | | 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, |
2675 | | 255}; |
2676 | | |
2677 | | static constexpr size_t maxdigits_u64[] = { |
2678 | | 64, 41, 32, 28, 25, 23, 22, 21, 20, 19, 18, 18, 17, 17, 16, 16, 16, 16, |
2679 | | 15, 15, 15, 15, 14, 14, 14, 14, 14, 14, 14, 13, 13, 13, 13, 13, 13}; |
2680 | | |
2681 | | static constexpr uint64_t min_safe_u64[] = { |
2682 | | 9223372036854775808ull, 12157665459056928801ull, 4611686018427387904, |
2683 | | 7450580596923828125, 4738381338321616896, 3909821048582988049, |
2684 | | 9223372036854775808ull, 12157665459056928801ull, 10000000000000000000ull, |
2685 | | 5559917313492231481, 2218611106740436992, 8650415919381337933, |
2686 | | 2177953337809371136, 6568408355712890625, 1152921504606846976, |
2687 | | 2862423051509815793, 6746640616477458432, 15181127029874798299ull, |
2688 | | 1638400000000000000, 3243919932521508681, 6221821273427820544, |
2689 | | 11592836324538749809ull, 876488338465357824, 1490116119384765625, |
2690 | | 2481152873203736576, 4052555153018976267, 6502111422497947648, |
2691 | | 10260628712958602189ull, 15943230000000000000ull, 787662783788549761, |
2692 | | 1152921504606846976, 1667889514952984961, 2386420683693101056, |
2693 | | 3379220508056640625, 4738381338321616896}; |
2694 | | }; |
2695 | | |
2696 | | #if FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE |
2697 | | |
2698 | | template <typename T> constexpr uint8_t int_luts<T>::chdigit[]; |
2699 | | |
2700 | | template <typename T> constexpr size_t int_luts<T>::maxdigits_u64[]; |
2701 | | |
2702 | | template <typename T> constexpr uint64_t int_luts<T>::min_safe_u64[]; |
2703 | | |
2704 | | #endif |
2705 | | |
2706 | | template <typename UC> |
2707 | | fastfloat_really_inline constexpr uint8_t ch_to_digit(UC c) { |
2708 | | return int_luts<>::chdigit[static_cast<unsigned char>(c)]; |
2709 | | } |
2710 | | |
2711 | 0 | fastfloat_really_inline constexpr size_t max_digits_u64(int base) { |
2712 | 0 | return int_luts<>::maxdigits_u64[base - 2]; |
2713 | 0 | } |
2714 | | |
2715 | | // If a u64 is exactly max_digits_u64() in length, this is |
2716 | | // the value below which it has definitely overflowed. |
2717 | 0 | fastfloat_really_inline constexpr uint64_t min_safe_u64(int base) { |
2718 | 0 | return int_luts<>::min_safe_u64[base - 2]; |
2719 | 0 | } |
2720 | | |
2721 | | static_assert(tinyobj_ff::is_same<equiv_uint_t<double>, uint64_t>::value, |
2722 | | "equiv_uint should be uint64_t for double"); |
2723 | | static_assert(std::numeric_limits<double>::is_iec559, |
2724 | | "double must fulfill the requirements of IEC 559 (IEEE 754)"); |
2725 | | |
2726 | | static_assert(tinyobj_ff::is_same<equiv_uint_t<float>, uint32_t>::value, |
2727 | | "equiv_uint should be uint32_t for float"); |
2728 | | static_assert(std::numeric_limits<float>::is_iec559, |
2729 | | "float must fulfill the requirements of IEC 559 (IEEE 754)"); |
2730 | | |
2731 | | #ifdef __STDCPP_FLOAT64_T__ |
2732 | | static_assert(tinyobj_ff::is_same<equiv_uint_t<std::float64_t>, uint64_t>::value, |
2733 | | "equiv_uint should be uint64_t for std::float64_t"); |
2734 | | static_assert( |
2735 | | std::numeric_limits<std::float64_t>::is_iec559, |
2736 | | "std::float64_t must fulfill the requirements of IEC 559 (IEEE 754)"); |
2737 | | #endif // __STDCPP_FLOAT64_T__ |
2738 | | |
2739 | | #ifdef __STDCPP_FLOAT32_T__ |
2740 | | static_assert(tinyobj_ff::is_same<equiv_uint_t<std::float32_t>, uint32_t>::value, |
2741 | | "equiv_uint should be uint32_t for std::float32_t"); |
2742 | | static_assert( |
2743 | | std::numeric_limits<std::float32_t>::is_iec559, |
2744 | | "std::float32_t must fulfill the requirements of IEC 559 (IEEE 754)"); |
2745 | | #endif // __STDCPP_FLOAT32_T__ |
2746 | | |
2747 | | #ifdef __STDCPP_FLOAT16_T__ |
2748 | | static_assert( |
2749 | | tinyobj_ff::is_same<binary_format<std::float16_t>::equiv_uint, uint16_t>::value, |
2750 | | "equiv_uint should be uint16_t for std::float16_t"); |
2751 | | static_assert( |
2752 | | std::numeric_limits<std::float16_t>::is_iec559, |
2753 | | "std::float16_t must fulfill the requirements of IEC 559 (IEEE 754)"); |
2754 | | #endif // __STDCPP_FLOAT16_T__ |
2755 | | |
2756 | | #ifdef __STDCPP_BFLOAT16_T__ |
2757 | | static_assert( |
2758 | | tinyobj_ff::is_same<binary_format<std::bfloat16_t>::equiv_uint, uint16_t>::value, |
2759 | | "equiv_uint should be uint16_t for std::bfloat16_t"); |
2760 | | static_assert( |
2761 | | std::numeric_limits<std::bfloat16_t>::is_iec559, |
2762 | | "std::bfloat16_t must fulfill the requirements of IEC 559 (IEEE 754)"); |
2763 | | #endif // __STDCPP_BFLOAT16_T__ |
2764 | | |
2765 | 0 | constexpr chars_format operator~(chars_format rhs) noexcept { |
2766 | 0 | using int_type = tinyobj_ff::underlying_type<chars_format>::type; |
2767 | 0 | return static_cast<chars_format>(~static_cast<int_type>(rhs)); |
2768 | 0 | } |
2769 | | |
2770 | 12.7M | constexpr chars_format operator&(chars_format lhs, chars_format rhs) noexcept { |
2771 | 12.7M | using int_type = tinyobj_ff::underlying_type<chars_format>::type; |
2772 | 12.7M | return static_cast<chars_format>(static_cast<int_type>(lhs) & |
2773 | 12.7M | static_cast<int_type>(rhs)); |
2774 | 12.7M | } |
2775 | | |
2776 | 1.87M | constexpr chars_format operator|(chars_format lhs, chars_format rhs) noexcept { |
2777 | 1.87M | using int_type = tinyobj_ff::underlying_type<chars_format>::type; |
2778 | 1.87M | return static_cast<chars_format>(static_cast<int_type>(lhs) | |
2779 | 1.87M | static_cast<int_type>(rhs)); |
2780 | 1.87M | } |
2781 | | |
2782 | 0 | constexpr chars_format operator^(chars_format lhs, chars_format rhs) noexcept { |
2783 | 0 | using int_type = tinyobj_ff::underlying_type<chars_format>::type; |
2784 | 0 | return static_cast<chars_format>(static_cast<int_type>(lhs) ^ |
2785 | 0 | static_cast<int_type>(rhs)); |
2786 | 0 | } |
2787 | | |
2788 | | fastfloat_really_inline FASTFLOAT_CONSTEXPR14 chars_format & |
2789 | 0 | operator&=(chars_format &lhs, chars_format rhs) noexcept { |
2790 | 0 | return lhs = (lhs & rhs); |
2791 | 0 | } |
2792 | | |
2793 | | fastfloat_really_inline FASTFLOAT_CONSTEXPR14 chars_format & |
2794 | 0 | operator|=(chars_format &lhs, chars_format rhs) noexcept { |
2795 | 0 | return lhs = (lhs | rhs); |
2796 | 0 | } |
2797 | | |
2798 | | fastfloat_really_inline FASTFLOAT_CONSTEXPR14 chars_format & |
2799 | 0 | operator^=(chars_format &lhs, chars_format rhs) noexcept { |
2800 | 0 | return lhs = (lhs ^ rhs); |
2801 | 0 | } |
2802 | | |
2803 | | namespace detail { |
2804 | | // adjust for deprecated feature macros |
2805 | 3.75M | constexpr chars_format adjust_for_feature_macros(chars_format fmt) { |
2806 | 3.75M | return fmt |
2807 | | #ifdef FASTFLOAT_ALLOWS_LEADING_PLUS |
2808 | | | chars_format::allow_leading_plus |
2809 | | #endif |
2810 | | #ifdef FASTFLOAT_SKIP_WHITE_SPACE |
2811 | | | chars_format::skip_white_space |
2812 | | #endif |
2813 | 3.75M | ; |
2814 | 3.75M | } |
2815 | | } // namespace detail |
2816 | | |
2817 | | } // namespace fast_float |
2818 | | |
2819 | | #endif |
2820 | | |
2821 | | |
2822 | | #ifndef FASTFLOAT_FAST_FLOAT_H |
2823 | | #define FASTFLOAT_FAST_FLOAT_H |
2824 | | |
2825 | | |
2826 | | namespace fast_float { |
2827 | | /** |
2828 | | * This function parses the character sequence [first,last) for a number. It |
2829 | | * parses floating-point numbers expecting a locale-indepent format equivalent |
2830 | | * to what is used by std::strtod in the default ("C") locale. The resulting |
2831 | | * floating-point value is the closest floating-point values (using either float |
2832 | | * or double), using the "round to even" convention for values that would |
2833 | | * otherwise fall right in-between two values. That is, we provide exact parsing |
2834 | | * according to the IEEE standard. |
2835 | | * |
2836 | | * Given a successful parse, the pointer (`ptr`) in the returned value is set to |
2837 | | * point right after the parsed number, and the `value` referenced is set to the |
2838 | | * parsed value. In case of error, the returned `ec` contains a representative |
2839 | | * error, otherwise the default (`tinyobj_ff::ff_errc()`) value is stored. |
2840 | | * |
2841 | | * The implementation does not throw and does not allocate memory (e.g., with |
2842 | | * `new` or `malloc`). |
2843 | | * |
2844 | | * Like the C++17 standard, the `fast_float::from_chars` functions take an |
2845 | | * optional last argument of the type `fast_float::chars_format`. It is a bitset |
2846 | | * value: we check whether `fmt & fast_float::chars_format::fixed` and `fmt & |
2847 | | * fast_float::chars_format::scientific` are set to determine whether we allow |
2848 | | * the fixed point and scientific notation respectively. The default is |
2849 | | * `fast_float::chars_format::general` which allows both `fixed` and |
2850 | | * `scientific`. |
2851 | | */ |
2852 | | template <typename T, typename UC = char, |
2853 | | typename = FASTFLOAT_ENABLE_IF(is_supported_float_type<T>::value)> |
2854 | | FASTFLOAT_CONSTEXPR20 from_chars_result_t<UC> |
2855 | | from_chars(UC const *first, UC const *last, T &value, |
2856 | | chars_format fmt = chars_format::general) noexcept; |
2857 | | |
2858 | | /** |
2859 | | * Like from_chars, but accepts an `options` argument to govern number parsing. |
2860 | | * Both for floating-point types and integer types. |
2861 | | */ |
2862 | | template <typename T, typename UC = char> |
2863 | | FASTFLOAT_CONSTEXPR20 from_chars_result_t<UC> |
2864 | | from_chars_advanced(UC const *first, UC const *last, T &value, |
2865 | | parse_options_t<UC> options) noexcept; |
2866 | | |
2867 | | /** |
2868 | | * from_chars for integer types. |
2869 | | */ |
2870 | | template <typename T, typename UC = char, |
2871 | | typename = FASTFLOAT_ENABLE_IF(is_supported_integer_type<T>::value)> |
2872 | | FASTFLOAT_CONSTEXPR20 from_chars_result_t<UC> |
2873 | | from_chars(UC const *first, UC const *last, T &value, int base = 10) noexcept; |
2874 | | |
2875 | | } // namespace fast_float |
2876 | | |
2877 | | #endif // FASTFLOAT_FAST_FLOAT_H |
2878 | | |
2879 | | #ifndef FASTFLOAT_ASCII_NUMBER_H |
2880 | | #define FASTFLOAT_ASCII_NUMBER_H |
2881 | | |
2882 | | #include <cctype> |
2883 | | #include <cstring> |
2884 | | #include <limits> |
2885 | | |
2886 | | |
2887 | | #ifdef FASTFLOAT_SSE2 |
2888 | | #include <emmintrin.h> |
2889 | | #endif |
2890 | | |
2891 | | #ifdef FASTFLOAT_NEON |
2892 | | #include <arm_neon.h> |
2893 | | #endif |
2894 | | |
2895 | | namespace fast_float { |
2896 | | |
2897 | 0 | template <typename UC> fastfloat_really_inline constexpr bool has_simd_opt() { |
2898 | 0 | #ifdef FASTFLOAT_HAS_SIMD |
2899 | 0 | return tinyobj_ff::is_same<UC, char16_t>::value; |
2900 | 0 | #else |
2901 | 0 | return false; |
2902 | 0 | #endif |
2903 | 0 | } |
2904 | | |
2905 | | // Next function can be micro-optimized, but compilers are entirely |
2906 | | // able to optimize it well. |
2907 | | template <typename UC> |
2908 | 10.3M | fastfloat_really_inline constexpr bool is_integer(UC c) noexcept { |
2909 | 10.3M | return !(c > UC('9') || c < UC('0')); |
2910 | 10.3M | } |
2911 | | |
2912 | 0 | fastfloat_really_inline constexpr uint64_t byteswap(uint64_t val) { |
2913 | 0 | return (val & 0xFF00000000000000) >> 56 | (val & 0x00FF000000000000) >> 40 | |
2914 | 0 | (val & 0x0000FF0000000000) >> 24 | (val & 0x000000FF00000000) >> 8 | |
2915 | 0 | (val & 0x00000000FF000000) << 8 | (val & 0x0000000000FF0000) << 24 | |
2916 | 0 | (val & 0x000000000000FF00) << 40 | (val & 0x00000000000000FF) << 56; |
2917 | 0 | } |
2918 | | |
2919 | | // Read 8 UC into a u64. Truncates UC if not char. |
2920 | | template <typename UC> |
2921 | | fastfloat_really_inline FASTFLOAT_CONSTEXPR20 uint64_t |
2922 | 2.60M | read8_to_u64(UC const *chars) { |
2923 | 2.60M | if (cpp20_and_in_constexpr() || !tinyobj_ff::is_same<UC, char>::value) { |
2924 | 0 | uint64_t val = 0; |
2925 | 0 | for (int i = 0; i < 8; ++i) { |
2926 | 0 | val |= uint64_t(uint8_t(*chars)) << (i * 8); |
2927 | 0 | ++chars; |
2928 | 0 | } |
2929 | 0 | return val; |
2930 | 0 | } |
2931 | 2.60M | uint64_t val; |
2932 | 2.60M | ::memcpy(&val, chars, sizeof(uint64_t)); |
2933 | | #if FASTFLOAT_IS_BIG_ENDIAN == 1 |
2934 | | // Need to read as-if the number was in little-endian order. |
2935 | | val = byteswap(val); |
2936 | | #endif |
2937 | 2.60M | return val; |
2938 | 2.60M | } |
2939 | | |
2940 | | #ifdef FASTFLOAT_SSE2 |
2941 | | |
2942 | 0 | fastfloat_really_inline uint64_t simd_read8_to_u64(__m128i const data) { |
2943 | 0 | FASTFLOAT_SIMD_DISABLE_WARNINGS |
2944 | 0 | __m128i const packed = _mm_packus_epi16(data, data); |
2945 | 0 | #ifdef FASTFLOAT_64BIT |
2946 | 0 | return uint64_t(_mm_cvtsi128_si64(packed)); |
2947 | 0 | #else |
2948 | 0 | uint64_t value; |
2949 | 0 | // Visual Studio + older versions of GCC don't support _mm_storeu_si64 |
2950 | 0 | _mm_storel_epi64(reinterpret_cast<__m128i *>(&value), packed); |
2951 | 0 | return value; |
2952 | 0 | #endif |
2953 | 0 | FASTFLOAT_SIMD_RESTORE_WARNINGS |
2954 | 0 | } |
2955 | | |
2956 | 0 | fastfloat_really_inline uint64_t simd_read8_to_u64(char16_t const *chars) { |
2957 | 0 | FASTFLOAT_SIMD_DISABLE_WARNINGS |
2958 | 0 | return simd_read8_to_u64( |
2959 | 0 | _mm_loadu_si128(reinterpret_cast<__m128i const *>(chars))); |
2960 | 0 | FASTFLOAT_SIMD_RESTORE_WARNINGS |
2961 | 0 | } |
2962 | | |
2963 | | #elif defined(FASTFLOAT_NEON) |
2964 | | |
2965 | | fastfloat_really_inline uint64_t simd_read8_to_u64(uint16x8_t const data) { |
2966 | | FASTFLOAT_SIMD_DISABLE_WARNINGS |
2967 | | uint8x8_t utf8_packed = vmovn_u16(data); |
2968 | | return vget_lane_u64(vreinterpret_u64_u8(utf8_packed), 0); |
2969 | | FASTFLOAT_SIMD_RESTORE_WARNINGS |
2970 | | } |
2971 | | |
2972 | | fastfloat_really_inline uint64_t simd_read8_to_u64(char16_t const *chars) { |
2973 | | FASTFLOAT_SIMD_DISABLE_WARNINGS |
2974 | | return simd_read8_to_u64( |
2975 | | vld1q_u16(reinterpret_cast<uint16_t const *>(chars))); |
2976 | | FASTFLOAT_SIMD_RESTORE_WARNINGS |
2977 | | } |
2978 | | |
2979 | | #endif // FASTFLOAT_SSE2 |
2980 | | |
2981 | | // MSVC SFINAE is broken pre-VS2017 |
2982 | | #if defined(_MSC_VER) && _MSC_VER <= 1900 |
2983 | | template <typename UC> |
2984 | | #else |
2985 | | template <typename UC, FASTFLOAT_ENABLE_IF(!has_simd_opt<UC>()) = 0> |
2986 | | #endif |
2987 | | // dummy for compile |
2988 | 0 | uint64_t simd_read8_to_u64(UC const *) { |
2989 | 0 | return 0; |
2990 | 0 | } |
2991 | | |
2992 | | // credit @aqrit |
2993 | | fastfloat_really_inline FASTFLOAT_CONSTEXPR14 uint32_t |
2994 | 1.66M | parse_eight_digits_unrolled(uint64_t val) { |
2995 | 1.66M | uint64_t const mask = 0x000000FF000000FF; |
2996 | 1.66M | uint64_t const mul1 = 0x000F424000000064; // 100 + (1000000ULL << 32) |
2997 | 1.66M | uint64_t const mul2 = 0x0000271000000001; // 1 + (10000ULL << 32) |
2998 | 1.66M | val -= 0x3030303030303030; |
2999 | 1.66M | val = (val * 10) + (val >> 8); // val = (val * 2561) >> 8; |
3000 | 1.66M | val = (((val & mask) * mul1) + (((val >> 16) & mask) * mul2)) >> 32; |
3001 | 1.66M | return uint32_t(val); |
3002 | 1.66M | } |
3003 | | |
3004 | | // Call this if chars are definitely 8 digits. |
3005 | | template <typename UC> |
3006 | | fastfloat_really_inline FASTFLOAT_CONSTEXPR20 uint32_t |
3007 | 763k | parse_eight_digits_unrolled(UC const *chars) noexcept { |
3008 | 763k | if (cpp20_and_in_constexpr() || !has_simd_opt<UC>()) { |
3009 | 763k | return parse_eight_digits_unrolled(read8_to_u64(chars)); // truncation okay |
3010 | 763k | } |
3011 | 0 | return parse_eight_digits_unrolled(simd_read8_to_u64(chars)); |
3012 | 763k | } |
3013 | | |
3014 | | // credit @aqrit |
3015 | | fastfloat_really_inline constexpr bool |
3016 | 938k | is_made_of_eight_digits_fast(uint64_t val) noexcept { |
3017 | 938k | return !((((val + 0x4646464646464646) | (val - 0x3030303030303030)) & |
3018 | 938k | 0x8080808080808080)); |
3019 | 938k | } |
3020 | | |
3021 | | #ifdef FASTFLOAT_HAS_SIMD |
3022 | | |
3023 | | // Call this if chars might not be 8 digits. |
3024 | | // Using this style (instead of is_made_of_eight_digits_fast() then |
3025 | | // parse_eight_digits_unrolled()) ensures we don't load SIMD registers twice. |
3026 | | fastfloat_really_inline FASTFLOAT_CONSTEXPR20 bool |
3027 | | simd_parse_if_eight_digits_unrolled(char16_t const *chars, |
3028 | 0 | uint64_t &i) noexcept { |
3029 | 0 | if (cpp20_and_in_constexpr()) { |
3030 | 0 | return false; |
3031 | 0 | } |
3032 | 0 | #ifdef FASTFLOAT_SSE2 |
3033 | 0 | FASTFLOAT_SIMD_DISABLE_WARNINGS |
3034 | 0 | __m128i const data = |
3035 | 0 | _mm_loadu_si128(reinterpret_cast<__m128i const *>(chars)); |
3036 | 0 |
|
3037 | 0 | // (x - '0') <= 9 |
3038 | 0 | // http://0x80.pl/articles/simd-parsing-int-sequences.html |
3039 | 0 | __m128i const t0 = _mm_add_epi16(data, _mm_set1_epi16(32720)); |
3040 | 0 | __m128i const t1 = _mm_cmpgt_epi16(t0, _mm_set1_epi16(-32759)); |
3041 | 0 |
|
3042 | 0 | if (_mm_movemask_epi8(t1) == 0) { |
3043 | 0 | i = i * 100000000 + parse_eight_digits_unrolled(simd_read8_to_u64(data)); |
3044 | 0 | return true; |
3045 | 0 | } else |
3046 | 0 | return false; |
3047 | 0 | FASTFLOAT_SIMD_RESTORE_WARNINGS |
3048 | 0 | #elif defined(FASTFLOAT_NEON) |
3049 | 0 | FASTFLOAT_SIMD_DISABLE_WARNINGS |
3050 | 0 | uint16x8_t const data = vld1q_u16(reinterpret_cast<uint16_t const *>(chars)); |
3051 | 0 |
|
3052 | 0 | // (x - '0') <= 9 |
3053 | 0 | // http://0x80.pl/articles/simd-parsing-int-sequences.html |
3054 | 0 | uint16x8_t const t0 = vsubq_u16(data, vmovq_n_u16('0')); |
3055 | 0 | uint16x8_t const mask = vcltq_u16(t0, vmovq_n_u16('9' - '0' + 1)); |
3056 | 0 |
|
3057 | 0 | if (vminvq_u16(mask) == 0xFFFF) { |
3058 | 0 | i = i * 100000000 + parse_eight_digits_unrolled(simd_read8_to_u64(data)); |
3059 | 0 | return true; |
3060 | 0 | } else |
3061 | 0 | return false; |
3062 | 0 | FASTFLOAT_SIMD_RESTORE_WARNINGS |
3063 | 0 | #else |
3064 | 0 | (void)chars; |
3065 | 0 | (void)i; |
3066 | 0 | return false; |
3067 | 0 | #endif // FASTFLOAT_SSE2 |
3068 | 0 | } |
3069 | | |
3070 | | #endif // FASTFLOAT_HAS_SIMD |
3071 | | |
3072 | | // MSVC SFINAE is broken pre-VS2017 |
3073 | | #if defined(_MSC_VER) && _MSC_VER <= 1900 |
3074 | | template <typename UC> |
3075 | | #else |
3076 | | template <typename UC, FASTFLOAT_ENABLE_IF(!has_simd_opt<UC>()) = 0> |
3077 | | #endif |
3078 | | // dummy for compile |
3079 | | bool simd_parse_if_eight_digits_unrolled(UC const *, uint64_t &) { |
3080 | | return 0; |
3081 | | } |
3082 | | |
3083 | | template <typename UC, FASTFLOAT_ENABLE_IF(!tinyobj_ff::is_same<UC, char>::value) = 0> |
3084 | | fastfloat_really_inline FASTFLOAT_CONSTEXPR20 void |
3085 | | loop_parse_if_eight_digits(UC const *&p, UC const *const pend, uint64_t &i) { |
3086 | | if (!has_simd_opt<UC>()) { |
3087 | | return; |
3088 | | } |
3089 | | while ((tinyobj_ff::distance(p, pend) >= 8) && |
3090 | | simd_parse_if_eight_digits_unrolled( |
3091 | | p, i)) { // in rare cases, this will overflow, but that's ok |
3092 | | p += 8; |
3093 | | } |
3094 | | } |
3095 | | |
3096 | | fastfloat_really_inline FASTFLOAT_CONSTEXPR20 void |
3097 | | loop_parse_if_eight_digits(char const *&p, char const *const pend, |
3098 | 81.4k | uint64_t &i) { |
3099 | | // optimizes better than parse_if_eight_digits_unrolled() for UC = char. |
3100 | 981k | while ((tinyobj_ff::distance(p, pend) >= 8) && |
3101 | 938k | is_made_of_eight_digits_fast(read8_to_u64(p))) { |
3102 | 899k | i = i * 100000000 + |
3103 | 899k | parse_eight_digits_unrolled(read8_to_u64( |
3104 | 899k | p)); // in rare cases, this will overflow, but that's ok |
3105 | 899k | p += 8; |
3106 | 899k | } |
3107 | 81.4k | } |
3108 | | |
3109 | | enum class parse_error { |
3110 | | no_error, |
3111 | | // [JSON-only] The minus sign must be followed by an integer. |
3112 | | missing_integer_after_sign, |
3113 | | // A sign must be followed by an integer or dot. |
3114 | | missing_integer_or_dot_after_sign, |
3115 | | // [JSON-only] The integer part must not have leading zeros. |
3116 | | leading_zeros_in_integer_part, |
3117 | | // [JSON-only] The integer part must have at least one digit. |
3118 | | no_digits_in_integer_part, |
3119 | | // [JSON-only] If there is a decimal point, there must be digits in the |
3120 | | // fractional part. |
3121 | | no_digits_in_fractional_part, |
3122 | | // The mantissa must have at least one digit. |
3123 | | no_digits_in_mantissa, |
3124 | | // Scientific notation requires an exponential part. |
3125 | | missing_exponential_part, |
3126 | | }; |
3127 | | |
3128 | | template <typename UC> struct parsed_number_string_t { |
3129 | | int64_t exponent{0}; |
3130 | | uint64_t mantissa{0}; |
3131 | | UC const *lastmatch{nullptr}; |
3132 | | bool negative{false}; |
3133 | | bool valid{false}; |
3134 | | bool too_many_digits{false}; |
3135 | | // contains the range of the significant digits |
3136 | | span<UC const> integer{}; // non-nullable |
3137 | | span<UC const> fraction{}; // nullable |
3138 | | parse_error error{parse_error::no_error}; |
3139 | | }; |
3140 | | |
3141 | | using byte_span = span<char const>; |
3142 | | using parsed_number_string = parsed_number_string_t<char>; |
3143 | | |
3144 | | template <typename UC> |
3145 | | fastfloat_really_inline FASTFLOAT_CONSTEXPR20 parsed_number_string_t<UC> |
3146 | 142k | report_parse_error(UC const *p, parse_error error) { |
3147 | 142k | parsed_number_string_t<UC> answer; |
3148 | 142k | answer.valid = false; |
3149 | 142k | answer.lastmatch = p; |
3150 | 142k | answer.error = error; |
3151 | 142k | return answer; |
3152 | 142k | } |
3153 | | |
3154 | | // Assuming that you use no more than 19 digits, this will |
3155 | | // parse an ASCII string. |
3156 | | template <bool basic_json_fmt, typename UC> |
3157 | | fastfloat_really_inline FASTFLOAT_CONSTEXPR20 parsed_number_string_t<UC> |
3158 | | parse_number_string(UC const *p, UC const *pend, |
3159 | 1.87M | parse_options_t<UC> options) noexcept { |
3160 | 1.87M | chars_format const fmt = detail::adjust_for_feature_macros(options.format); |
3161 | 1.87M | UC const decimal_point = options.decimal_point; |
3162 | | |
3163 | 1.87M | parsed_number_string_t<UC> answer; |
3164 | 1.87M | answer.valid = false; |
3165 | 1.87M | answer.too_many_digits = false; |
3166 | | // assume p < pend, so dereference without checks; |
3167 | 1.87M | answer.negative = (*p == UC('-')); |
3168 | | // C++17 20.19.3.(7.1) explicitly forbids '+' sign here |
3169 | 1.87M | if ((*p == UC('-')) || (uint64_t(fmt & chars_format::allow_leading_plus) && |
3170 | 1.83M | !basic_json_fmt && *p == UC('+'))) { |
3171 | 55.9k | ++p; |
3172 | 55.9k | if (p == pend) { |
3173 | 9.80k | return report_parse_error<UC>( |
3174 | 9.80k | p, parse_error::missing_integer_or_dot_after_sign); |
3175 | 9.80k | } |
3176 | 46.1k | FASTFLOAT_IF_CONSTEXPR17(basic_json_fmt) { |
3177 | 0 | if (!is_integer(*p)) { // a sign must be followed by an integer |
3178 | 0 | return report_parse_error<UC>(p, |
3179 | 0 | parse_error::missing_integer_after_sign); |
3180 | 0 | } |
3181 | 0 | } |
3182 | 46.1k | else { |
3183 | 46.1k | if (!is_integer(*p) && |
3184 | 30.2k | (*p != |
3185 | 30.2k | decimal_point)) { // a sign must be followed by an integer or the dot |
3186 | 30.0k | return report_parse_error<UC>( |
3187 | 30.0k | p, parse_error::missing_integer_or_dot_after_sign); |
3188 | 30.0k | } |
3189 | 46.1k | } |
3190 | 46.1k | } |
3191 | 1.83M | UC const *const start_digits = p; |
3192 | | |
3193 | 1.83M | uint64_t i = 0; // an unsigned int avoids signed overflows (which are bad) |
3194 | | |
3195 | 11.2M | while ((p != pend) && is_integer(*p)) { |
3196 | | // a multiplication by 10 is cheaper than an arbitrary integer |
3197 | | // multiplication |
3198 | 9.38M | i = 10 * i + |
3199 | 9.38M | uint64_t(*p - |
3200 | 9.38M | UC('0')); // might overflow, we will handle the overflow later |
3201 | 9.38M | ++p; |
3202 | 9.38M | } |
3203 | 1.83M | UC const *const end_of_integer_part = p; |
3204 | 1.83M | int64_t digit_count = int64_t(end_of_integer_part - start_digits); |
3205 | 1.83M | answer.integer = span<UC const>(start_digits, size_t(digit_count)); |
3206 | 1.83M | FASTFLOAT_IF_CONSTEXPR17(basic_json_fmt) { |
3207 | | // at least 1 digit in integer part, without leading zeros |
3208 | 0 | if (digit_count == 0) { |
3209 | 0 | return report_parse_error<UC>(p, parse_error::no_digits_in_integer_part); |
3210 | 0 | } |
3211 | 0 | if ((start_digits[0] == UC('0') && digit_count > 1)) { |
3212 | 0 | return report_parse_error<UC>(start_digits, |
3213 | 0 | parse_error::leading_zeros_in_integer_part); |
3214 | 0 | } |
3215 | 0 | } |
3216 | | |
3217 | 1.83M | int64_t exponent = 0; |
3218 | 1.83M | bool const has_decimal_point = (p != pend) && (*p == decimal_point); |
3219 | 1.83M | if (has_decimal_point) { |
3220 | 81.4k | ++p; |
3221 | 81.4k | UC const *before = p; |
3222 | | // can occur at most twice without overflowing, but let it occur more, since |
3223 | | // for integers with many digits, digit parsing is the primary bottleneck. |
3224 | 81.4k | loop_parse_if_eight_digits(p, pend, i); |
3225 | | |
3226 | 302k | while ((p != pend) && is_integer(*p)) { |
3227 | 220k | uint8_t digit = uint8_t(*p - UC('0')); |
3228 | 220k | ++p; |
3229 | 220k | i = i * 10 + digit; // in rare cases, this will overflow, but that's ok |
3230 | 220k | } |
3231 | 81.4k | exponent = before - p; |
3232 | 81.4k | answer.fraction = span<UC const>(before, size_t(p - before)); |
3233 | 81.4k | digit_count -= exponent; |
3234 | 81.4k | } |
3235 | 1.83M | FASTFLOAT_IF_CONSTEXPR17(basic_json_fmt) { |
3236 | | // at least 1 digit in fractional part |
3237 | 0 | if (has_decimal_point && exponent == 0) { |
3238 | 0 | return report_parse_error<UC>(p, |
3239 | 0 | parse_error::no_digits_in_fractional_part); |
3240 | 0 | } |
3241 | 0 | } |
3242 | 1.83M | else if (digit_count == 0) { // we must have encountered at least one integer! |
3243 | 102k | return report_parse_error<UC>(p, parse_error::no_digits_in_mantissa); |
3244 | 102k | } |
3245 | 1.73M | int64_t exp_number = 0; // explicit exponential part |
3246 | 1.73M | if ((uint64_t(fmt & chars_format::scientific) && (p != pend) && |
3247 | 348k | ((UC('e') == *p) || (UC('E') == *p))) || |
3248 | 1.70M | (uint64_t(fmt & detail::basic_fortran_fmt) && (p != pend) && |
3249 | 0 | ((UC('+') == *p) || (UC('-') == *p) || (UC('d') == *p) || |
3250 | 26.1k | (UC('D') == *p)))) { |
3251 | 26.1k | UC const *location_of_e = p; |
3252 | 26.1k | if ((UC('e') == *p) || (UC('E') == *p) || (UC('d') == *p) || |
3253 | 26.1k | (UC('D') == *p)) { |
3254 | 26.1k | ++p; |
3255 | 26.1k | } |
3256 | 26.1k | bool neg_exp = false; |
3257 | 26.1k | if ((p != pend) && (UC('-') == *p)) { |
3258 | 8.83k | neg_exp = true; |
3259 | 8.83k | ++p; |
3260 | 17.3k | } else if ((p != pend) && |
3261 | 13.5k | (UC('+') == |
3262 | 13.5k | *p)) { // '+' on exponent is allowed by C++17 20.19.3.(7.1) |
3263 | 3.45k | ++p; |
3264 | 3.45k | } |
3265 | 26.1k | if ((p == pend) || !is_integer(*p)) { |
3266 | 9.43k | if (!uint64_t(fmt & chars_format::fixed)) { |
3267 | | // The exponential part is invalid for scientific notation, so it must |
3268 | | // be a trailing token for fixed notation. However, fixed notation is |
3269 | | // disabled, so report a scientific notation error. |
3270 | 0 | return report_parse_error<UC>(p, parse_error::missing_exponential_part); |
3271 | 0 | } |
3272 | | // Otherwise, we will be ignoring the 'e'. |
3273 | 9.43k | p = location_of_e; |
3274 | 16.7k | } else { |
3275 | 159k | while ((p != pend) && is_integer(*p)) { |
3276 | 142k | uint8_t digit = uint8_t(*p - UC('0')); |
3277 | 142k | if (exp_number < 0x10000000) { |
3278 | 130k | exp_number = 10 * exp_number + digit; |
3279 | 130k | } |
3280 | 142k | ++p; |
3281 | 142k | } |
3282 | 16.7k | if (neg_exp) { |
3283 | 8.73k | exp_number = -exp_number; |
3284 | 8.73k | } |
3285 | 16.7k | exponent += exp_number; |
3286 | 16.7k | } |
3287 | 1.70M | } else { |
3288 | | // If it scientific and not fixed, we have to bail out. |
3289 | 1.70M | if (uint64_t(fmt & chars_format::scientific) && |
3290 | 1.70M | !uint64_t(fmt & chars_format::fixed)) { |
3291 | 0 | return report_parse_error<UC>(p, parse_error::missing_exponential_part); |
3292 | 0 | } |
3293 | 1.70M | } |
3294 | 1.73M | answer.lastmatch = p; |
3295 | 1.73M | answer.valid = true; |
3296 | | |
3297 | | // If we frequently had to deal with long strings of digits, |
3298 | | // we could extend our code by using a 128-bit integer instead |
3299 | | // of a 64-bit integer. However, this is uncommon. |
3300 | | // |
3301 | | // We can deal with up to 19 digits. |
3302 | 1.73M | if (digit_count > 19) { // this is uncommon |
3303 | | // It is possible that the integer had an overflow. |
3304 | | // We have to handle the case where we have 0.0000somenumber. |
3305 | | // We need to be mindful of the case where we only have zeroes... |
3306 | | // E.g., 0.000000000...000. |
3307 | 75.5k | UC const *start = start_digits; |
3308 | 801k | while ((start != pend) && (*start == UC('0') || *start == decimal_point)) { |
3309 | 726k | if (*start == UC('0')) { |
3310 | 714k | digit_count--; |
3311 | 714k | } |
3312 | 726k | start++; |
3313 | 726k | } |
3314 | | |
3315 | 75.5k | if (digit_count > 19) { |
3316 | 68.9k | answer.too_many_digits = true; |
3317 | | // Let us start again, this time, avoiding overflows. |
3318 | | // We don't need to check if is_integer, since we use the |
3319 | | // pre-tokenized spans from above. |
3320 | 68.9k | i = 0; |
3321 | 68.9k | p = answer.integer.ptr; |
3322 | 68.9k | UC const *int_end = p + answer.integer.len(); |
3323 | 68.9k | uint64_t const minimal_nineteen_digit_integer{1000000000000000000}; |
3324 | 1.18M | while ((i < minimal_nineteen_digit_integer) && (p != int_end)) { |
3325 | 1.11M | i = i * 10 + uint64_t(*p - UC('0')); |
3326 | 1.11M | ++p; |
3327 | 1.11M | } |
3328 | 68.9k | if (i >= minimal_nineteen_digit_integer) { // We have a big integers |
3329 | 38.0k | exponent = end_of_integer_part - p + exp_number; |
3330 | 38.0k | } else { // We have a value with a fractional component. |
3331 | 30.8k | p = answer.fraction.ptr; |
3332 | 30.8k | UC const *frac_end = p + answer.fraction.len(); |
3333 | 554k | while ((i < minimal_nineteen_digit_integer) && (p != frac_end)) { |
3334 | 523k | i = i * 10 + uint64_t(*p - UC('0')); |
3335 | 523k | ++p; |
3336 | 523k | } |
3337 | 30.8k | exponent = answer.fraction.ptr - p + exp_number; |
3338 | 30.8k | } |
3339 | | // We have now corrected both exponent and i, to a truncated value |
3340 | 68.9k | } |
3341 | 75.5k | } |
3342 | 1.73M | answer.exponent = exponent; |
3343 | 1.73M | answer.mantissa = i; |
3344 | 1.73M | return answer; |
3345 | 1.73M | } Unexecuted instantiation: fast_float::parsed_number_string_t<char> fast_float::parse_number_string<true, char>(char const*, char const*, fast_float::parse_options_t<char>) fast_float::parsed_number_string_t<char> fast_float::parse_number_string<false, char>(char const*, char const*, fast_float::parse_options_t<char>) Line | Count | Source | 3159 | 1.87M | parse_options_t<UC> options) noexcept { | 3160 | 1.87M | chars_format const fmt = detail::adjust_for_feature_macros(options.format); | 3161 | 1.87M | UC const decimal_point = options.decimal_point; | 3162 | | | 3163 | 1.87M | parsed_number_string_t<UC> answer; | 3164 | 1.87M | answer.valid = false; | 3165 | 1.87M | answer.too_many_digits = false; | 3166 | | // assume p < pend, so dereference without checks; | 3167 | 1.87M | answer.negative = (*p == UC('-')); | 3168 | | // C++17 20.19.3.(7.1) explicitly forbids '+' sign here | 3169 | 1.87M | if ((*p == UC('-')) || (uint64_t(fmt & chars_format::allow_leading_plus) && | 3170 | 1.83M | !basic_json_fmt && *p == UC('+'))) { | 3171 | 55.9k | ++p; | 3172 | 55.9k | if (p == pend) { | 3173 | 9.80k | return report_parse_error<UC>( | 3174 | 9.80k | p, parse_error::missing_integer_or_dot_after_sign); | 3175 | 9.80k | } | 3176 | 46.1k | FASTFLOAT_IF_CONSTEXPR17(basic_json_fmt) { | 3177 | 0 | if (!is_integer(*p)) { // a sign must be followed by an integer | 3178 | 0 | return report_parse_error<UC>(p, | 3179 | 0 | parse_error::missing_integer_after_sign); | 3180 | 0 | } | 3181 | 0 | } | 3182 | 46.1k | else { | 3183 | 46.1k | if (!is_integer(*p) && | 3184 | 30.2k | (*p != | 3185 | 30.2k | decimal_point)) { // a sign must be followed by an integer or the dot | 3186 | 30.0k | return report_parse_error<UC>( | 3187 | 30.0k | p, parse_error::missing_integer_or_dot_after_sign); | 3188 | 30.0k | } | 3189 | 46.1k | } | 3190 | 46.1k | } | 3191 | 1.83M | UC const *const start_digits = p; | 3192 | | | 3193 | 1.83M | uint64_t i = 0; // an unsigned int avoids signed overflows (which are bad) | 3194 | | | 3195 | 11.2M | while ((p != pend) && is_integer(*p)) { | 3196 | | // a multiplication by 10 is cheaper than an arbitrary integer | 3197 | | // multiplication | 3198 | 9.38M | i = 10 * i + | 3199 | 9.38M | uint64_t(*p - | 3200 | 9.38M | UC('0')); // might overflow, we will handle the overflow later | 3201 | 9.38M | ++p; | 3202 | 9.38M | } | 3203 | 1.83M | UC const *const end_of_integer_part = p; | 3204 | 1.83M | int64_t digit_count = int64_t(end_of_integer_part - start_digits); | 3205 | 1.83M | answer.integer = span<UC const>(start_digits, size_t(digit_count)); | 3206 | 1.83M | FASTFLOAT_IF_CONSTEXPR17(basic_json_fmt) { | 3207 | | // at least 1 digit in integer part, without leading zeros | 3208 | 0 | if (digit_count == 0) { | 3209 | 0 | return report_parse_error<UC>(p, parse_error::no_digits_in_integer_part); | 3210 | 0 | } | 3211 | 0 | if ((start_digits[0] == UC('0') && digit_count > 1)) { | 3212 | 0 | return report_parse_error<UC>(start_digits, | 3213 | 0 | parse_error::leading_zeros_in_integer_part); | 3214 | 0 | } | 3215 | 0 | } | 3216 | | | 3217 | 1.83M | int64_t exponent = 0; | 3218 | 1.83M | bool const has_decimal_point = (p != pend) && (*p == decimal_point); | 3219 | 1.83M | if (has_decimal_point) { | 3220 | 81.4k | ++p; | 3221 | 81.4k | UC const *before = p; | 3222 | | // can occur at most twice without overflowing, but let it occur more, since | 3223 | | // for integers with many digits, digit parsing is the primary bottleneck. | 3224 | 81.4k | loop_parse_if_eight_digits(p, pend, i); | 3225 | | | 3226 | 302k | while ((p != pend) && is_integer(*p)) { | 3227 | 220k | uint8_t digit = uint8_t(*p - UC('0')); | 3228 | 220k | ++p; | 3229 | 220k | i = i * 10 + digit; // in rare cases, this will overflow, but that's ok | 3230 | 220k | } | 3231 | 81.4k | exponent = before - p; | 3232 | 81.4k | answer.fraction = span<UC const>(before, size_t(p - before)); | 3233 | 81.4k | digit_count -= exponent; | 3234 | 81.4k | } | 3235 | 1.83M | FASTFLOAT_IF_CONSTEXPR17(basic_json_fmt) { | 3236 | | // at least 1 digit in fractional part | 3237 | 0 | if (has_decimal_point && exponent == 0) { | 3238 | 0 | return report_parse_error<UC>(p, | 3239 | 0 | parse_error::no_digits_in_fractional_part); | 3240 | 0 | } | 3241 | 0 | } | 3242 | 1.83M | else if (digit_count == 0) { // we must have encountered at least one integer! | 3243 | 102k | return report_parse_error<UC>(p, parse_error::no_digits_in_mantissa); | 3244 | 102k | } | 3245 | 1.73M | int64_t exp_number = 0; // explicit exponential part | 3246 | 1.73M | if ((uint64_t(fmt & chars_format::scientific) && (p != pend) && | 3247 | 348k | ((UC('e') == *p) || (UC('E') == *p))) || | 3248 | 1.70M | (uint64_t(fmt & detail::basic_fortran_fmt) && (p != pend) && | 3249 | 0 | ((UC('+') == *p) || (UC('-') == *p) || (UC('d') == *p) || | 3250 | 26.1k | (UC('D') == *p)))) { | 3251 | 26.1k | UC const *location_of_e = p; | 3252 | 26.1k | if ((UC('e') == *p) || (UC('E') == *p) || (UC('d') == *p) || | 3253 | 26.1k | (UC('D') == *p)) { | 3254 | 26.1k | ++p; | 3255 | 26.1k | } | 3256 | 26.1k | bool neg_exp = false; | 3257 | 26.1k | if ((p != pend) && (UC('-') == *p)) { | 3258 | 8.83k | neg_exp = true; | 3259 | 8.83k | ++p; | 3260 | 17.3k | } else if ((p != pend) && | 3261 | 13.5k | (UC('+') == | 3262 | 13.5k | *p)) { // '+' on exponent is allowed by C++17 20.19.3.(7.1) | 3263 | 3.45k | ++p; | 3264 | 3.45k | } | 3265 | 26.1k | if ((p == pend) || !is_integer(*p)) { | 3266 | 9.43k | if (!uint64_t(fmt & chars_format::fixed)) { | 3267 | | // The exponential part is invalid for scientific notation, so it must | 3268 | | // be a trailing token for fixed notation. However, fixed notation is | 3269 | | // disabled, so report a scientific notation error. | 3270 | 0 | return report_parse_error<UC>(p, parse_error::missing_exponential_part); | 3271 | 0 | } | 3272 | | // Otherwise, we will be ignoring the 'e'. | 3273 | 9.43k | p = location_of_e; | 3274 | 16.7k | } else { | 3275 | 159k | while ((p != pend) && is_integer(*p)) { | 3276 | 142k | uint8_t digit = uint8_t(*p - UC('0')); | 3277 | 142k | if (exp_number < 0x10000000) { | 3278 | 130k | exp_number = 10 * exp_number + digit; | 3279 | 130k | } | 3280 | 142k | ++p; | 3281 | 142k | } | 3282 | 16.7k | if (neg_exp) { | 3283 | 8.73k | exp_number = -exp_number; | 3284 | 8.73k | } | 3285 | 16.7k | exponent += exp_number; | 3286 | 16.7k | } | 3287 | 1.70M | } else { | 3288 | | // If it scientific and not fixed, we have to bail out. | 3289 | 1.70M | if (uint64_t(fmt & chars_format::scientific) && | 3290 | 1.70M | !uint64_t(fmt & chars_format::fixed)) { | 3291 | 0 | return report_parse_error<UC>(p, parse_error::missing_exponential_part); | 3292 | 0 | } | 3293 | 1.70M | } | 3294 | 1.73M | answer.lastmatch = p; | 3295 | 1.73M | answer.valid = true; | 3296 | | | 3297 | | // If we frequently had to deal with long strings of digits, | 3298 | | // we could extend our code by using a 128-bit integer instead | 3299 | | // of a 64-bit integer. However, this is uncommon. | 3300 | | // | 3301 | | // We can deal with up to 19 digits. | 3302 | 1.73M | if (digit_count > 19) { // this is uncommon | 3303 | | // It is possible that the integer had an overflow. | 3304 | | // We have to handle the case where we have 0.0000somenumber. | 3305 | | // We need to be mindful of the case where we only have zeroes... | 3306 | | // E.g., 0.000000000...000. | 3307 | 75.5k | UC const *start = start_digits; | 3308 | 801k | while ((start != pend) && (*start == UC('0') || *start == decimal_point)) { | 3309 | 726k | if (*start == UC('0')) { | 3310 | 714k | digit_count--; | 3311 | 714k | } | 3312 | 726k | start++; | 3313 | 726k | } | 3314 | | | 3315 | 75.5k | if (digit_count > 19) { | 3316 | 68.9k | answer.too_many_digits = true; | 3317 | | // Let us start again, this time, avoiding overflows. | 3318 | | // We don't need to check if is_integer, since we use the | 3319 | | // pre-tokenized spans from above. | 3320 | 68.9k | i = 0; | 3321 | 68.9k | p = answer.integer.ptr; | 3322 | 68.9k | UC const *int_end = p + answer.integer.len(); | 3323 | 68.9k | uint64_t const minimal_nineteen_digit_integer{1000000000000000000}; | 3324 | 1.18M | while ((i < minimal_nineteen_digit_integer) && (p != int_end)) { | 3325 | 1.11M | i = i * 10 + uint64_t(*p - UC('0')); | 3326 | 1.11M | ++p; | 3327 | 1.11M | } | 3328 | 68.9k | if (i >= minimal_nineteen_digit_integer) { // We have a big integers | 3329 | 38.0k | exponent = end_of_integer_part - p + exp_number; | 3330 | 38.0k | } else { // We have a value with a fractional component. | 3331 | 30.8k | p = answer.fraction.ptr; | 3332 | 30.8k | UC const *frac_end = p + answer.fraction.len(); | 3333 | 554k | while ((i < minimal_nineteen_digit_integer) && (p != frac_end)) { | 3334 | 523k | i = i * 10 + uint64_t(*p - UC('0')); | 3335 | 523k | ++p; | 3336 | 523k | } | 3337 | 30.8k | exponent = answer.fraction.ptr - p + exp_number; | 3338 | 30.8k | } | 3339 | | // We have now corrected both exponent and i, to a truncated value | 3340 | 68.9k | } | 3341 | 75.5k | } | 3342 | 1.73M | answer.exponent = exponent; | 3343 | 1.73M | answer.mantissa = i; | 3344 | 1.73M | return answer; | 3345 | 1.73M | } |
|
3346 | | |
3347 | | template <typename T, typename UC> |
3348 | | fastfloat_really_inline FASTFLOAT_CONSTEXPR20 from_chars_result_t<UC> |
3349 | | parse_int_string(UC const *p, UC const *pend, T &value, |
3350 | | parse_options_t<UC> options) { |
3351 | | chars_format const fmt = detail::adjust_for_feature_macros(options.format); |
3352 | | int const base = options.base; |
3353 | | |
3354 | | from_chars_result_t<UC> answer; |
3355 | | |
3356 | | UC const *const first = p; |
3357 | | |
3358 | | bool const negative = (*p == UC('-')); |
3359 | | #ifdef FASTFLOAT_VISUAL_STUDIO |
3360 | | #pragma warning(push) |
3361 | | #pragma warning(disable : 4127) |
3362 | | #endif |
3363 | | if (!tinyobj_ff::is_signed<T>::value && negative) { |
3364 | | #ifdef FASTFLOAT_VISUAL_STUDIO |
3365 | | #pragma warning(pop) |
3366 | | #endif |
3367 | | answer.ec = tinyobj_ff::ff_errc::invalid_argument; |
3368 | | answer.ptr = first; |
3369 | | return answer; |
3370 | | } |
3371 | | if ((*p == UC('-')) || |
3372 | | (uint64_t(fmt & chars_format::allow_leading_plus) && (*p == UC('+')))) { |
3373 | | ++p; |
3374 | | } |
3375 | | |
3376 | | UC const *const start_num = p; |
3377 | | |
3378 | | while (p != pend && *p == UC('0')) { |
3379 | | ++p; |
3380 | | } |
3381 | | |
3382 | | bool const has_leading_zeros = p > start_num; |
3383 | | |
3384 | | UC const *const start_digits = p; |
3385 | | |
3386 | | uint64_t i = 0; |
3387 | | if (base == 10) { |
3388 | | loop_parse_if_eight_digits(p, pend, i); // use SIMD if possible |
3389 | | } |
3390 | | while (p != pend) { |
3391 | | uint8_t digit = ch_to_digit(*p); |
3392 | | if (digit >= base) { |
3393 | | break; |
3394 | | } |
3395 | | i = uint64_t(base) * i + digit; // might overflow, check this later |
3396 | | p++; |
3397 | | } |
3398 | | |
3399 | | size_t digit_count = size_t(p - start_digits); |
3400 | | |
3401 | | if (digit_count == 0) { |
3402 | | if (has_leading_zeros) { |
3403 | | value = 0; |
3404 | | answer.ec = tinyobj_ff::ff_errc(); |
3405 | | answer.ptr = p; |
3406 | | } else { |
3407 | | answer.ec = tinyobj_ff::ff_errc::invalid_argument; |
3408 | | answer.ptr = first; |
3409 | | } |
3410 | | return answer; |
3411 | | } |
3412 | | |
3413 | | answer.ptr = p; |
3414 | | |
3415 | | // check u64 overflow |
3416 | | size_t max_digits = max_digits_u64(base); |
3417 | | if (digit_count > max_digits) { |
3418 | | answer.ec = tinyobj_ff::ff_errc::result_out_of_range; |
3419 | | return answer; |
3420 | | } |
3421 | | // this check can be eliminated for all other types, but they will all require |
3422 | | // a max_digits(base) equivalent |
3423 | | if (digit_count == max_digits && i < min_safe_u64(base)) { |
3424 | | answer.ec = tinyobj_ff::ff_errc::result_out_of_range; |
3425 | | return answer; |
3426 | | } |
3427 | | |
3428 | | // check other types overflow |
3429 | | if (!tinyobj_ff::is_same<T, uint64_t>::value) { |
3430 | | if (i > uint64_t(std::numeric_limits<T>::max()) + uint64_t(negative)) { |
3431 | | answer.ec = tinyobj_ff::ff_errc::result_out_of_range; |
3432 | | return answer; |
3433 | | } |
3434 | | } |
3435 | | |
3436 | | if (negative) { |
3437 | | #ifdef FASTFLOAT_VISUAL_STUDIO |
3438 | | #pragma warning(push) |
3439 | | #pragma warning(disable : 4146) |
3440 | | #endif |
3441 | | // this weird workaround is required because: |
3442 | | // - converting unsigned to signed when its value is greater than signed max |
3443 | | // is UB pre-C++23. |
3444 | | // - reinterpret_casting (~i + 1) would work, but it is not constexpr |
3445 | | // this is always optimized into a neg instruction (note: T is an integer |
3446 | | // type) |
3447 | | value = T(-std::numeric_limits<T>::max() - |
3448 | | T(i - uint64_t(std::numeric_limits<T>::max()))); |
3449 | | #ifdef FASTFLOAT_VISUAL_STUDIO |
3450 | | #pragma warning(pop) |
3451 | | #endif |
3452 | | } else { |
3453 | | value = T(i); |
3454 | | } |
3455 | | |
3456 | | answer.ec = tinyobj_ff::ff_errc(); |
3457 | | return answer; |
3458 | | } |
3459 | | |
3460 | | } // namespace fast_float |
3461 | | |
3462 | | #endif |
3463 | | |
3464 | | #ifndef FASTFLOAT_FAST_TABLE_H |
3465 | | #define FASTFLOAT_FAST_TABLE_H |
3466 | | |
3467 | | namespace fast_float { |
3468 | | |
3469 | | /** |
3470 | | * When mapping numbers from decimal to binary, |
3471 | | * we go from w * 10^q to m * 2^p but we have |
3472 | | * 10^q = 5^q * 2^q, so effectively |
3473 | | * we are trying to match |
3474 | | * w * 2^q * 5^q to m * 2^p. Thus the powers of two |
3475 | | * are not a concern since they can be represented |
3476 | | * exactly using the binary notation, only the powers of five |
3477 | | * affect the binary significand. |
3478 | | */ |
3479 | | |
3480 | | /** |
3481 | | * The smallest non-zero float (binary64) is 2^-1074. |
3482 | | * We take as input numbers of the form w x 10^q where w < 2^64. |
3483 | | * We have that w * 10^-343 < 2^(64-344) 5^-343 < 2^-1076. |
3484 | | * However, we have that |
3485 | | * (2^64-1) * 10^-342 = (2^64-1) * 2^-342 * 5^-342 > 2^-1074. |
3486 | | * Thus it is possible for a number of the form w * 10^-342 where |
3487 | | * w is a 64-bit value to be a non-zero floating-point number. |
3488 | | ********* |
3489 | | * Any number of form w * 10^309 where w>= 1 is going to be |
3490 | | * infinite in binary64 so we never need to worry about powers |
3491 | | * of 5 greater than 308. |
3492 | | */ |
3493 | | template <class unused = void> struct powers_template { |
3494 | | |
3495 | | constexpr static int smallest_power_of_five = |
3496 | | binary_format<double>::smallest_power_of_ten(); |
3497 | | constexpr static int largest_power_of_five = |
3498 | | binary_format<double>::largest_power_of_ten(); |
3499 | | constexpr static int number_of_entries = |
3500 | | 2 * (largest_power_of_five - smallest_power_of_five + 1); |
3501 | | // Powers of five from 5^-342 all the way to 5^308 rounded toward one. |
3502 | | constexpr static uint64_t power_of_five_128[number_of_entries] = { |
3503 | | 0xeef453d6923bd65a, 0x113faa2906a13b3f, |
3504 | | 0x9558b4661b6565f8, 0x4ac7ca59a424c507, |
3505 | | 0xbaaee17fa23ebf76, 0x5d79bcf00d2df649, |
3506 | | 0xe95a99df8ace6f53, 0xf4d82c2c107973dc, |
3507 | | 0x91d8a02bb6c10594, 0x79071b9b8a4be869, |
3508 | | 0xb64ec836a47146f9, 0x9748e2826cdee284, |
3509 | | 0xe3e27a444d8d98b7, 0xfd1b1b2308169b25, |
3510 | | 0x8e6d8c6ab0787f72, 0xfe30f0f5e50e20f7, |
3511 | | 0xb208ef855c969f4f, 0xbdbd2d335e51a935, |
3512 | | 0xde8b2b66b3bc4723, 0xad2c788035e61382, |
3513 | | 0x8b16fb203055ac76, 0x4c3bcb5021afcc31, |
3514 | | 0xaddcb9e83c6b1793, 0xdf4abe242a1bbf3d, |
3515 | | 0xd953e8624b85dd78, 0xd71d6dad34a2af0d, |
3516 | | 0x87d4713d6f33aa6b, 0x8672648c40e5ad68, |
3517 | | 0xa9c98d8ccb009506, 0x680efdaf511f18c2, |
3518 | | 0xd43bf0effdc0ba48, 0x212bd1b2566def2, |
3519 | | 0x84a57695fe98746d, 0x14bb630f7604b57, |
3520 | | 0xa5ced43b7e3e9188, 0x419ea3bd35385e2d, |
3521 | | 0xcf42894a5dce35ea, 0x52064cac828675b9, |
3522 | | 0x818995ce7aa0e1b2, 0x7343efebd1940993, |
3523 | | 0xa1ebfb4219491a1f, 0x1014ebe6c5f90bf8, |
3524 | | 0xca66fa129f9b60a6, 0xd41a26e077774ef6, |
3525 | | 0xfd00b897478238d0, 0x8920b098955522b4, |
3526 | | 0x9e20735e8cb16382, 0x55b46e5f5d5535b0, |
3527 | | 0xc5a890362fddbc62, 0xeb2189f734aa831d, |
3528 | | 0xf712b443bbd52b7b, 0xa5e9ec7501d523e4, |
3529 | | 0x9a6bb0aa55653b2d, 0x47b233c92125366e, |
3530 | | 0xc1069cd4eabe89f8, 0x999ec0bb696e840a, |
3531 | | 0xf148440a256e2c76, 0xc00670ea43ca250d, |
3532 | | 0x96cd2a865764dbca, 0x380406926a5e5728, |
3533 | | 0xbc807527ed3e12bc, 0xc605083704f5ecf2, |
3534 | | 0xeba09271e88d976b, 0xf7864a44c633682e, |
3535 | | 0x93445b8731587ea3, 0x7ab3ee6afbe0211d, |
3536 | | 0xb8157268fdae9e4c, 0x5960ea05bad82964, |
3537 | | 0xe61acf033d1a45df, 0x6fb92487298e33bd, |
3538 | | 0x8fd0c16206306bab, 0xa5d3b6d479f8e056, |
3539 | | 0xb3c4f1ba87bc8696, 0x8f48a4899877186c, |
3540 | | 0xe0b62e2929aba83c, 0x331acdabfe94de87, |
3541 | | 0x8c71dcd9ba0b4925, 0x9ff0c08b7f1d0b14, |
3542 | | 0xaf8e5410288e1b6f, 0x7ecf0ae5ee44dd9, |
3543 | | 0xdb71e91432b1a24a, 0xc9e82cd9f69d6150, |
3544 | | 0x892731ac9faf056e, 0xbe311c083a225cd2, |
3545 | | 0xab70fe17c79ac6ca, 0x6dbd630a48aaf406, |
3546 | | 0xd64d3d9db981787d, 0x92cbbccdad5b108, |
3547 | | 0x85f0468293f0eb4e, 0x25bbf56008c58ea5, |
3548 | | 0xa76c582338ed2621, 0xaf2af2b80af6f24e, |
3549 | | 0xd1476e2c07286faa, 0x1af5af660db4aee1, |
3550 | | 0x82cca4db847945ca, 0x50d98d9fc890ed4d, |
3551 | | 0xa37fce126597973c, 0xe50ff107bab528a0, |
3552 | | 0xcc5fc196fefd7d0c, 0x1e53ed49a96272c8, |
3553 | | 0xff77b1fcbebcdc4f, 0x25e8e89c13bb0f7a, |
3554 | | 0x9faacf3df73609b1, 0x77b191618c54e9ac, |
3555 | | 0xc795830d75038c1d, 0xd59df5b9ef6a2417, |
3556 | | 0xf97ae3d0d2446f25, 0x4b0573286b44ad1d, |
3557 | | 0x9becce62836ac577, 0x4ee367f9430aec32, |
3558 | | 0xc2e801fb244576d5, 0x229c41f793cda73f, |
3559 | | 0xf3a20279ed56d48a, 0x6b43527578c1110f, |
3560 | | 0x9845418c345644d6, 0x830a13896b78aaa9, |
3561 | | 0xbe5691ef416bd60c, 0x23cc986bc656d553, |
3562 | | 0xedec366b11c6cb8f, 0x2cbfbe86b7ec8aa8, |
3563 | | 0x94b3a202eb1c3f39, 0x7bf7d71432f3d6a9, |
3564 | | 0xb9e08a83a5e34f07, 0xdaf5ccd93fb0cc53, |
3565 | | 0xe858ad248f5c22c9, 0xd1b3400f8f9cff68, |
3566 | | 0x91376c36d99995be, 0x23100809b9c21fa1, |
3567 | | 0xb58547448ffffb2d, 0xabd40a0c2832a78a, |
3568 | | 0xe2e69915b3fff9f9, 0x16c90c8f323f516c, |
3569 | | 0x8dd01fad907ffc3b, 0xae3da7d97f6792e3, |
3570 | | 0xb1442798f49ffb4a, 0x99cd11cfdf41779c, |
3571 | | 0xdd95317f31c7fa1d, 0x40405643d711d583, |
3572 | | 0x8a7d3eef7f1cfc52, 0x482835ea666b2572, |
3573 | | 0xad1c8eab5ee43b66, 0xda3243650005eecf, |
3574 | | 0xd863b256369d4a40, 0x90bed43e40076a82, |
3575 | | 0x873e4f75e2224e68, 0x5a7744a6e804a291, |
3576 | | 0xa90de3535aaae202, 0x711515d0a205cb36, |
3577 | | 0xd3515c2831559a83, 0xd5a5b44ca873e03, |
3578 | | 0x8412d9991ed58091, 0xe858790afe9486c2, |
3579 | | 0xa5178fff668ae0b6, 0x626e974dbe39a872, |
3580 | | 0xce5d73ff402d98e3, 0xfb0a3d212dc8128f, |
3581 | | 0x80fa687f881c7f8e, 0x7ce66634bc9d0b99, |
3582 | | 0xa139029f6a239f72, 0x1c1fffc1ebc44e80, |
3583 | | 0xc987434744ac874e, 0xa327ffb266b56220, |
3584 | | 0xfbe9141915d7a922, 0x4bf1ff9f0062baa8, |
3585 | | 0x9d71ac8fada6c9b5, 0x6f773fc3603db4a9, |
3586 | | 0xc4ce17b399107c22, 0xcb550fb4384d21d3, |
3587 | | 0xf6019da07f549b2b, 0x7e2a53a146606a48, |
3588 | | 0x99c102844f94e0fb, 0x2eda7444cbfc426d, |
3589 | | 0xc0314325637a1939, 0xfa911155fefb5308, |
3590 | | 0xf03d93eebc589f88, 0x793555ab7eba27ca, |
3591 | | 0x96267c7535b763b5, 0x4bc1558b2f3458de, |
3592 | | 0xbbb01b9283253ca2, 0x9eb1aaedfb016f16, |
3593 | | 0xea9c227723ee8bcb, 0x465e15a979c1cadc, |
3594 | | 0x92a1958a7675175f, 0xbfacd89ec191ec9, |
3595 | | 0xb749faed14125d36, 0xcef980ec671f667b, |
3596 | | 0xe51c79a85916f484, 0x82b7e12780e7401a, |
3597 | | 0x8f31cc0937ae58d2, 0xd1b2ecb8b0908810, |
3598 | | 0xb2fe3f0b8599ef07, 0x861fa7e6dcb4aa15, |
3599 | | 0xdfbdcece67006ac9, 0x67a791e093e1d49a, |
3600 | | 0x8bd6a141006042bd, 0xe0c8bb2c5c6d24e0, |
3601 | | 0xaecc49914078536d, 0x58fae9f773886e18, |
3602 | | 0xda7f5bf590966848, 0xaf39a475506a899e, |
3603 | | 0x888f99797a5e012d, 0x6d8406c952429603, |
3604 | | 0xaab37fd7d8f58178, 0xc8e5087ba6d33b83, |
3605 | | 0xd5605fcdcf32e1d6, 0xfb1e4a9a90880a64, |
3606 | | 0x855c3be0a17fcd26, 0x5cf2eea09a55067f, |
3607 | | 0xa6b34ad8c9dfc06f, 0xf42faa48c0ea481e, |
3608 | | 0xd0601d8efc57b08b, 0xf13b94daf124da26, |
3609 | | 0x823c12795db6ce57, 0x76c53d08d6b70858, |
3610 | | 0xa2cb1717b52481ed, 0x54768c4b0c64ca6e, |
3611 | | 0xcb7ddcdda26da268, 0xa9942f5dcf7dfd09, |
3612 | | 0xfe5d54150b090b02, 0xd3f93b35435d7c4c, |
3613 | | 0x9efa548d26e5a6e1, 0xc47bc5014a1a6daf, |
3614 | | 0xc6b8e9b0709f109a, 0x359ab6419ca1091b, |
3615 | | 0xf867241c8cc6d4c0, 0xc30163d203c94b62, |
3616 | | 0x9b407691d7fc44f8, 0x79e0de63425dcf1d, |
3617 | | 0xc21094364dfb5636, 0x985915fc12f542e4, |
3618 | | 0xf294b943e17a2bc4, 0x3e6f5b7b17b2939d, |
3619 | | 0x979cf3ca6cec5b5a, 0xa705992ceecf9c42, |
3620 | | 0xbd8430bd08277231, 0x50c6ff782a838353, |
3621 | | 0xece53cec4a314ebd, 0xa4f8bf5635246428, |
3622 | | 0x940f4613ae5ed136, 0x871b7795e136be99, |
3623 | | 0xb913179899f68584, 0x28e2557b59846e3f, |
3624 | | 0xe757dd7ec07426e5, 0x331aeada2fe589cf, |
3625 | | 0x9096ea6f3848984f, 0x3ff0d2c85def7621, |
3626 | | 0xb4bca50b065abe63, 0xfed077a756b53a9, |
3627 | | 0xe1ebce4dc7f16dfb, 0xd3e8495912c62894, |
3628 | | 0x8d3360f09cf6e4bd, 0x64712dd7abbbd95c, |
3629 | | 0xb080392cc4349dec, 0xbd8d794d96aacfb3, |
3630 | | 0xdca04777f541c567, 0xecf0d7a0fc5583a0, |
3631 | | 0x89e42caaf9491b60, 0xf41686c49db57244, |
3632 | | 0xac5d37d5b79b6239, 0x311c2875c522ced5, |
3633 | | 0xd77485cb25823ac7, 0x7d633293366b828b, |
3634 | | 0x86a8d39ef77164bc, 0xae5dff9c02033197, |
3635 | | 0xa8530886b54dbdeb, 0xd9f57f830283fdfc, |
3636 | | 0xd267caa862a12d66, 0xd072df63c324fd7b, |
3637 | | 0x8380dea93da4bc60, 0x4247cb9e59f71e6d, |
3638 | | 0xa46116538d0deb78, 0x52d9be85f074e608, |
3639 | | 0xcd795be870516656, 0x67902e276c921f8b, |
3640 | | 0x806bd9714632dff6, 0xba1cd8a3db53b6, |
3641 | | 0xa086cfcd97bf97f3, 0x80e8a40eccd228a4, |
3642 | | 0xc8a883c0fdaf7df0, 0x6122cd128006b2cd, |
3643 | | 0xfad2a4b13d1b5d6c, 0x796b805720085f81, |
3644 | | 0x9cc3a6eec6311a63, 0xcbe3303674053bb0, |
3645 | | 0xc3f490aa77bd60fc, 0xbedbfc4411068a9c, |
3646 | | 0xf4f1b4d515acb93b, 0xee92fb5515482d44, |
3647 | | 0x991711052d8bf3c5, 0x751bdd152d4d1c4a, |
3648 | | 0xbf5cd54678eef0b6, 0xd262d45a78a0635d, |
3649 | | 0xef340a98172aace4, 0x86fb897116c87c34, |
3650 | | 0x9580869f0e7aac0e, 0xd45d35e6ae3d4da0, |
3651 | | 0xbae0a846d2195712, 0x8974836059cca109, |
3652 | | 0xe998d258869facd7, 0x2bd1a438703fc94b, |
3653 | | 0x91ff83775423cc06, 0x7b6306a34627ddcf, |
3654 | | 0xb67f6455292cbf08, 0x1a3bc84c17b1d542, |
3655 | | 0xe41f3d6a7377eeca, 0x20caba5f1d9e4a93, |
3656 | | 0x8e938662882af53e, 0x547eb47b7282ee9c, |
3657 | | 0xb23867fb2a35b28d, 0xe99e619a4f23aa43, |
3658 | | 0xdec681f9f4c31f31, 0x6405fa00e2ec94d4, |
3659 | | 0x8b3c113c38f9f37e, 0xde83bc408dd3dd04, |
3660 | | 0xae0b158b4738705e, 0x9624ab50b148d445, |
3661 | | 0xd98ddaee19068c76, 0x3badd624dd9b0957, |
3662 | | 0x87f8a8d4cfa417c9, 0xe54ca5d70a80e5d6, |
3663 | | 0xa9f6d30a038d1dbc, 0x5e9fcf4ccd211f4c, |
3664 | | 0xd47487cc8470652b, 0x7647c3200069671f, |
3665 | | 0x84c8d4dfd2c63f3b, 0x29ecd9f40041e073, |
3666 | | 0xa5fb0a17c777cf09, 0xf468107100525890, |
3667 | | 0xcf79cc9db955c2cc, 0x7182148d4066eeb4, |
3668 | | 0x81ac1fe293d599bf, 0xc6f14cd848405530, |
3669 | | 0xa21727db38cb002f, 0xb8ada00e5a506a7c, |
3670 | | 0xca9cf1d206fdc03b, 0xa6d90811f0e4851c, |
3671 | | 0xfd442e4688bd304a, 0x908f4a166d1da663, |
3672 | | 0x9e4a9cec15763e2e, 0x9a598e4e043287fe, |
3673 | | 0xc5dd44271ad3cdba, 0x40eff1e1853f29fd, |
3674 | | 0xf7549530e188c128, 0xd12bee59e68ef47c, |
3675 | | 0x9a94dd3e8cf578b9, 0x82bb74f8301958ce, |
3676 | | 0xc13a148e3032d6e7, 0xe36a52363c1faf01, |
3677 | | 0xf18899b1bc3f8ca1, 0xdc44e6c3cb279ac1, |
3678 | | 0x96f5600f15a7b7e5, 0x29ab103a5ef8c0b9, |
3679 | | 0xbcb2b812db11a5de, 0x7415d448f6b6f0e7, |
3680 | | 0xebdf661791d60f56, 0x111b495b3464ad21, |
3681 | | 0x936b9fcebb25c995, 0xcab10dd900beec34, |
3682 | | 0xb84687c269ef3bfb, 0x3d5d514f40eea742, |
3683 | | 0xe65829b3046b0afa, 0xcb4a5a3112a5112, |
3684 | | 0x8ff71a0fe2c2e6dc, 0x47f0e785eaba72ab, |
3685 | | 0xb3f4e093db73a093, 0x59ed216765690f56, |
3686 | | 0xe0f218b8d25088b8, 0x306869c13ec3532c, |
3687 | | 0x8c974f7383725573, 0x1e414218c73a13fb, |
3688 | | 0xafbd2350644eeacf, 0xe5d1929ef90898fa, |
3689 | | 0xdbac6c247d62a583, 0xdf45f746b74abf39, |
3690 | | 0x894bc396ce5da772, 0x6b8bba8c328eb783, |
3691 | | 0xab9eb47c81f5114f, 0x66ea92f3f326564, |
3692 | | 0xd686619ba27255a2, 0xc80a537b0efefebd, |
3693 | | 0x8613fd0145877585, 0xbd06742ce95f5f36, |
3694 | | 0xa798fc4196e952e7, 0x2c48113823b73704, |
3695 | | 0xd17f3b51fca3a7a0, 0xf75a15862ca504c5, |
3696 | | 0x82ef85133de648c4, 0x9a984d73dbe722fb, |
3697 | | 0xa3ab66580d5fdaf5, 0xc13e60d0d2e0ebba, |
3698 | | 0xcc963fee10b7d1b3, 0x318df905079926a8, |
3699 | | 0xffbbcfe994e5c61f, 0xfdf17746497f7052, |
3700 | | 0x9fd561f1fd0f9bd3, 0xfeb6ea8bedefa633, |
3701 | | 0xc7caba6e7c5382c8, 0xfe64a52ee96b8fc0, |
3702 | | 0xf9bd690a1b68637b, 0x3dfdce7aa3c673b0, |
3703 | | 0x9c1661a651213e2d, 0x6bea10ca65c084e, |
3704 | | 0xc31bfa0fe5698db8, 0x486e494fcff30a62, |
3705 | | 0xf3e2f893dec3f126, 0x5a89dba3c3efccfa, |
3706 | | 0x986ddb5c6b3a76b7, 0xf89629465a75e01c, |
3707 | | 0xbe89523386091465, 0xf6bbb397f1135823, |
3708 | | 0xee2ba6c0678b597f, 0x746aa07ded582e2c, |
3709 | | 0x94db483840b717ef, 0xa8c2a44eb4571cdc, |
3710 | | 0xba121a4650e4ddeb, 0x92f34d62616ce413, |
3711 | | 0xe896a0d7e51e1566, 0x77b020baf9c81d17, |
3712 | | 0x915e2486ef32cd60, 0xace1474dc1d122e, |
3713 | | 0xb5b5ada8aaff80b8, 0xd819992132456ba, |
3714 | | 0xe3231912d5bf60e6, 0x10e1fff697ed6c69, |
3715 | | 0x8df5efabc5979c8f, 0xca8d3ffa1ef463c1, |
3716 | | 0xb1736b96b6fd83b3, 0xbd308ff8a6b17cb2, |
3717 | | 0xddd0467c64bce4a0, 0xac7cb3f6d05ddbde, |
3718 | | 0x8aa22c0dbef60ee4, 0x6bcdf07a423aa96b, |
3719 | | 0xad4ab7112eb3929d, 0x86c16c98d2c953c6, |
3720 | | 0xd89d64d57a607744, 0xe871c7bf077ba8b7, |
3721 | | 0x87625f056c7c4a8b, 0x11471cd764ad4972, |
3722 | | 0xa93af6c6c79b5d2d, 0xd598e40d3dd89bcf, |
3723 | | 0xd389b47879823479, 0x4aff1d108d4ec2c3, |
3724 | | 0x843610cb4bf160cb, 0xcedf722a585139ba, |
3725 | | 0xa54394fe1eedb8fe, 0xc2974eb4ee658828, |
3726 | | 0xce947a3da6a9273e, 0x733d226229feea32, |
3727 | | 0x811ccc668829b887, 0x806357d5a3f525f, |
3728 | | 0xa163ff802a3426a8, 0xca07c2dcb0cf26f7, |
3729 | | 0xc9bcff6034c13052, 0xfc89b393dd02f0b5, |
3730 | | 0xfc2c3f3841f17c67, 0xbbac2078d443ace2, |
3731 | | 0x9d9ba7832936edc0, 0xd54b944b84aa4c0d, |
3732 | | 0xc5029163f384a931, 0xa9e795e65d4df11, |
3733 | | 0xf64335bcf065d37d, 0x4d4617b5ff4a16d5, |
3734 | | 0x99ea0196163fa42e, 0x504bced1bf8e4e45, |
3735 | | 0xc06481fb9bcf8d39, 0xe45ec2862f71e1d6, |
3736 | | 0xf07da27a82c37088, 0x5d767327bb4e5a4c, |
3737 | | 0x964e858c91ba2655, 0x3a6a07f8d510f86f, |
3738 | | 0xbbe226efb628afea, 0x890489f70a55368b, |
3739 | | 0xeadab0aba3b2dbe5, 0x2b45ac74ccea842e, |
3740 | | 0x92c8ae6b464fc96f, 0x3b0b8bc90012929d, |
3741 | | 0xb77ada0617e3bbcb, 0x9ce6ebb40173744, |
3742 | | 0xe55990879ddcaabd, 0xcc420a6a101d0515, |
3743 | | 0x8f57fa54c2a9eab6, 0x9fa946824a12232d, |
3744 | | 0xb32df8e9f3546564, 0x47939822dc96abf9, |
3745 | | 0xdff9772470297ebd, 0x59787e2b93bc56f7, |
3746 | | 0x8bfbea76c619ef36, 0x57eb4edb3c55b65a, |
3747 | | 0xaefae51477a06b03, 0xede622920b6b23f1, |
3748 | | 0xdab99e59958885c4, 0xe95fab368e45eced, |
3749 | | 0x88b402f7fd75539b, 0x11dbcb0218ebb414, |
3750 | | 0xaae103b5fcd2a881, 0xd652bdc29f26a119, |
3751 | | 0xd59944a37c0752a2, 0x4be76d3346f0495f, |
3752 | | 0x857fcae62d8493a5, 0x6f70a4400c562ddb, |
3753 | | 0xa6dfbd9fb8e5b88e, 0xcb4ccd500f6bb952, |
3754 | | 0xd097ad07a71f26b2, 0x7e2000a41346a7a7, |
3755 | | 0x825ecc24c873782f, 0x8ed400668c0c28c8, |
3756 | | 0xa2f67f2dfa90563b, 0x728900802f0f32fa, |
3757 | | 0xcbb41ef979346bca, 0x4f2b40a03ad2ffb9, |
3758 | | 0xfea126b7d78186bc, 0xe2f610c84987bfa8, |
3759 | | 0x9f24b832e6b0f436, 0xdd9ca7d2df4d7c9, |
3760 | | 0xc6ede63fa05d3143, 0x91503d1c79720dbb, |
3761 | | 0xf8a95fcf88747d94, 0x75a44c6397ce912a, |
3762 | | 0x9b69dbe1b548ce7c, 0xc986afbe3ee11aba, |
3763 | | 0xc24452da229b021b, 0xfbe85badce996168, |
3764 | | 0xf2d56790ab41c2a2, 0xfae27299423fb9c3, |
3765 | | 0x97c560ba6b0919a5, 0xdccd879fc967d41a, |
3766 | | 0xbdb6b8e905cb600f, 0x5400e987bbc1c920, |
3767 | | 0xed246723473e3813, 0x290123e9aab23b68, |
3768 | | 0x9436c0760c86e30b, 0xf9a0b6720aaf6521, |
3769 | | 0xb94470938fa89bce, 0xf808e40e8d5b3e69, |
3770 | | 0xe7958cb87392c2c2, 0xb60b1d1230b20e04, |
3771 | | 0x90bd77f3483bb9b9, 0xb1c6f22b5e6f48c2, |
3772 | | 0xb4ecd5f01a4aa828, 0x1e38aeb6360b1af3, |
3773 | | 0xe2280b6c20dd5232, 0x25c6da63c38de1b0, |
3774 | | 0x8d590723948a535f, 0x579c487e5a38ad0e, |
3775 | | 0xb0af48ec79ace837, 0x2d835a9df0c6d851, |
3776 | | 0xdcdb1b2798182244, 0xf8e431456cf88e65, |
3777 | | 0x8a08f0f8bf0f156b, 0x1b8e9ecb641b58ff, |
3778 | | 0xac8b2d36eed2dac5, 0xe272467e3d222f3f, |
3779 | | 0xd7adf884aa879177, 0x5b0ed81dcc6abb0f, |
3780 | | 0x86ccbb52ea94baea, 0x98e947129fc2b4e9, |
3781 | | 0xa87fea27a539e9a5, 0x3f2398d747b36224, |
3782 | | 0xd29fe4b18e88640e, 0x8eec7f0d19a03aad, |
3783 | | 0x83a3eeeef9153e89, 0x1953cf68300424ac, |
3784 | | 0xa48ceaaab75a8e2b, 0x5fa8c3423c052dd7, |
3785 | | 0xcdb02555653131b6, 0x3792f412cb06794d, |
3786 | | 0x808e17555f3ebf11, 0xe2bbd88bbee40bd0, |
3787 | | 0xa0b19d2ab70e6ed6, 0x5b6aceaeae9d0ec4, |
3788 | | 0xc8de047564d20a8b, 0xf245825a5a445275, |
3789 | | 0xfb158592be068d2e, 0xeed6e2f0f0d56712, |
3790 | | 0x9ced737bb6c4183d, 0x55464dd69685606b, |
3791 | | 0xc428d05aa4751e4c, 0xaa97e14c3c26b886, |
3792 | | 0xf53304714d9265df, 0xd53dd99f4b3066a8, |
3793 | | 0x993fe2c6d07b7fab, 0xe546a8038efe4029, |
3794 | | 0xbf8fdb78849a5f96, 0xde98520472bdd033, |
3795 | | 0xef73d256a5c0f77c, 0x963e66858f6d4440, |
3796 | | 0x95a8637627989aad, 0xdde7001379a44aa8, |
3797 | | 0xbb127c53b17ec159, 0x5560c018580d5d52, |
3798 | | 0xe9d71b689dde71af, 0xaab8f01e6e10b4a6, |
3799 | | 0x9226712162ab070d, 0xcab3961304ca70e8, |
3800 | | 0xb6b00d69bb55c8d1, 0x3d607b97c5fd0d22, |
3801 | | 0xe45c10c42a2b3b05, 0x8cb89a7db77c506a, |
3802 | | 0x8eb98a7a9a5b04e3, 0x77f3608e92adb242, |
3803 | | 0xb267ed1940f1c61c, 0x55f038b237591ed3, |
3804 | | 0xdf01e85f912e37a3, 0x6b6c46dec52f6688, |
3805 | | 0x8b61313bbabce2c6, 0x2323ac4b3b3da015, |
3806 | | 0xae397d8aa96c1b77, 0xabec975e0a0d081a, |
3807 | | 0xd9c7dced53c72255, 0x96e7bd358c904a21, |
3808 | | 0x881cea14545c7575, 0x7e50d64177da2e54, |
3809 | | 0xaa242499697392d2, 0xdde50bd1d5d0b9e9, |
3810 | | 0xd4ad2dbfc3d07787, 0x955e4ec64b44e864, |
3811 | | 0x84ec3c97da624ab4, 0xbd5af13bef0b113e, |
3812 | | 0xa6274bbdd0fadd61, 0xecb1ad8aeacdd58e, |
3813 | | 0xcfb11ead453994ba, 0x67de18eda5814af2, |
3814 | | 0x81ceb32c4b43fcf4, 0x80eacf948770ced7, |
3815 | | 0xa2425ff75e14fc31, 0xa1258379a94d028d, |
3816 | | 0xcad2f7f5359a3b3e, 0x96ee45813a04330, |
3817 | | 0xfd87b5f28300ca0d, 0x8bca9d6e188853fc, |
3818 | | 0x9e74d1b791e07e48, 0x775ea264cf55347e, |
3819 | | 0xc612062576589dda, 0x95364afe032a819e, |
3820 | | 0xf79687aed3eec551, 0x3a83ddbd83f52205, |
3821 | | 0x9abe14cd44753b52, 0xc4926a9672793543, |
3822 | | 0xc16d9a0095928a27, 0x75b7053c0f178294, |
3823 | | 0xf1c90080baf72cb1, 0x5324c68b12dd6339, |
3824 | | 0x971da05074da7bee, 0xd3f6fc16ebca5e04, |
3825 | | 0xbce5086492111aea, 0x88f4bb1ca6bcf585, |
3826 | | 0xec1e4a7db69561a5, 0x2b31e9e3d06c32e6, |
3827 | | 0x9392ee8e921d5d07, 0x3aff322e62439fd0, |
3828 | | 0xb877aa3236a4b449, 0x9befeb9fad487c3, |
3829 | | 0xe69594bec44de15b, 0x4c2ebe687989a9b4, |
3830 | | 0x901d7cf73ab0acd9, 0xf9d37014bf60a11, |
3831 | | 0xb424dc35095cd80f, 0x538484c19ef38c95, |
3832 | | 0xe12e13424bb40e13, 0x2865a5f206b06fba, |
3833 | | 0x8cbccc096f5088cb, 0xf93f87b7442e45d4, |
3834 | | 0xafebff0bcb24aafe, 0xf78f69a51539d749, |
3835 | | 0xdbe6fecebdedd5be, 0xb573440e5a884d1c, |
3836 | | 0x89705f4136b4a597, 0x31680a88f8953031, |
3837 | | 0xabcc77118461cefc, 0xfdc20d2b36ba7c3e, |
3838 | | 0xd6bf94d5e57a42bc, 0x3d32907604691b4d, |
3839 | | 0x8637bd05af6c69b5, 0xa63f9a49c2c1b110, |
3840 | | 0xa7c5ac471b478423, 0xfcf80dc33721d54, |
3841 | | 0xd1b71758e219652b, 0xd3c36113404ea4a9, |
3842 | | 0x83126e978d4fdf3b, 0x645a1cac083126ea, |
3843 | | 0xa3d70a3d70a3d70a, 0x3d70a3d70a3d70a4, |
3844 | | 0xcccccccccccccccc, 0xcccccccccccccccd, |
3845 | | 0x8000000000000000, 0x0, |
3846 | | 0xa000000000000000, 0x0, |
3847 | | 0xc800000000000000, 0x0, |
3848 | | 0xfa00000000000000, 0x0, |
3849 | | 0x9c40000000000000, 0x0, |
3850 | | 0xc350000000000000, 0x0, |
3851 | | 0xf424000000000000, 0x0, |
3852 | | 0x9896800000000000, 0x0, |
3853 | | 0xbebc200000000000, 0x0, |
3854 | | 0xee6b280000000000, 0x0, |
3855 | | 0x9502f90000000000, 0x0, |
3856 | | 0xba43b74000000000, 0x0, |
3857 | | 0xe8d4a51000000000, 0x0, |
3858 | | 0x9184e72a00000000, 0x0, |
3859 | | 0xb5e620f480000000, 0x0, |
3860 | | 0xe35fa931a0000000, 0x0, |
3861 | | 0x8e1bc9bf04000000, 0x0, |
3862 | | 0xb1a2bc2ec5000000, 0x0, |
3863 | | 0xde0b6b3a76400000, 0x0, |
3864 | | 0x8ac7230489e80000, 0x0, |
3865 | | 0xad78ebc5ac620000, 0x0, |
3866 | | 0xd8d726b7177a8000, 0x0, |
3867 | | 0x878678326eac9000, 0x0, |
3868 | | 0xa968163f0a57b400, 0x0, |
3869 | | 0xd3c21bcecceda100, 0x0, |
3870 | | 0x84595161401484a0, 0x0, |
3871 | | 0xa56fa5b99019a5c8, 0x0, |
3872 | | 0xcecb8f27f4200f3a, 0x0, |
3873 | | 0x813f3978f8940984, 0x4000000000000000, |
3874 | | 0xa18f07d736b90be5, 0x5000000000000000, |
3875 | | 0xc9f2c9cd04674ede, 0xa400000000000000, |
3876 | | 0xfc6f7c4045812296, 0x4d00000000000000, |
3877 | | 0x9dc5ada82b70b59d, 0xf020000000000000, |
3878 | | 0xc5371912364ce305, 0x6c28000000000000, |
3879 | | 0xf684df56c3e01bc6, 0xc732000000000000, |
3880 | | 0x9a130b963a6c115c, 0x3c7f400000000000, |
3881 | | 0xc097ce7bc90715b3, 0x4b9f100000000000, |
3882 | | 0xf0bdc21abb48db20, 0x1e86d40000000000, |
3883 | | 0x96769950b50d88f4, 0x1314448000000000, |
3884 | | 0xbc143fa4e250eb31, 0x17d955a000000000, |
3885 | | 0xeb194f8e1ae525fd, 0x5dcfab0800000000, |
3886 | | 0x92efd1b8d0cf37be, 0x5aa1cae500000000, |
3887 | | 0xb7abc627050305ad, 0xf14a3d9e40000000, |
3888 | | 0xe596b7b0c643c719, 0x6d9ccd05d0000000, |
3889 | | 0x8f7e32ce7bea5c6f, 0xe4820023a2000000, |
3890 | | 0xb35dbf821ae4f38b, 0xdda2802c8a800000, |
3891 | | 0xe0352f62a19e306e, 0xd50b2037ad200000, |
3892 | | 0x8c213d9da502de45, 0x4526f422cc340000, |
3893 | | 0xaf298d050e4395d6, 0x9670b12b7f410000, |
3894 | | 0xdaf3f04651d47b4c, 0x3c0cdd765f114000, |
3895 | | 0x88d8762bf324cd0f, 0xa5880a69fb6ac800, |
3896 | | 0xab0e93b6efee0053, 0x8eea0d047a457a00, |
3897 | | 0xd5d238a4abe98068, 0x72a4904598d6d880, |
3898 | | 0x85a36366eb71f041, 0x47a6da2b7f864750, |
3899 | | 0xa70c3c40a64e6c51, 0x999090b65f67d924, |
3900 | | 0xd0cf4b50cfe20765, 0xfff4b4e3f741cf6d, |
3901 | | 0x82818f1281ed449f, 0xbff8f10e7a8921a4, |
3902 | | 0xa321f2d7226895c7, 0xaff72d52192b6a0d, |
3903 | | 0xcbea6f8ceb02bb39, 0x9bf4f8a69f764490, |
3904 | | 0xfee50b7025c36a08, 0x2f236d04753d5b4, |
3905 | | 0x9f4f2726179a2245, 0x1d762422c946590, |
3906 | | 0xc722f0ef9d80aad6, 0x424d3ad2b7b97ef5, |
3907 | | 0xf8ebad2b84e0d58b, 0xd2e0898765a7deb2, |
3908 | | 0x9b934c3b330c8577, 0x63cc55f49f88eb2f, |
3909 | | 0xc2781f49ffcfa6d5, 0x3cbf6b71c76b25fb, |
3910 | | 0xf316271c7fc3908a, 0x8bef464e3945ef7a, |
3911 | | 0x97edd871cfda3a56, 0x97758bf0e3cbb5ac, |
3912 | | 0xbde94e8e43d0c8ec, 0x3d52eeed1cbea317, |
3913 | | 0xed63a231d4c4fb27, 0x4ca7aaa863ee4bdd, |
3914 | | 0x945e455f24fb1cf8, 0x8fe8caa93e74ef6a, |
3915 | | 0xb975d6b6ee39e436, 0xb3e2fd538e122b44, |
3916 | | 0xe7d34c64a9c85d44, 0x60dbbca87196b616, |
3917 | | 0x90e40fbeea1d3a4a, 0xbc8955e946fe31cd, |
3918 | | 0xb51d13aea4a488dd, 0x6babab6398bdbe41, |
3919 | | 0xe264589a4dcdab14, 0xc696963c7eed2dd1, |
3920 | | 0x8d7eb76070a08aec, 0xfc1e1de5cf543ca2, |
3921 | | 0xb0de65388cc8ada8, 0x3b25a55f43294bcb, |
3922 | | 0xdd15fe86affad912, 0x49ef0eb713f39ebe, |
3923 | | 0x8a2dbf142dfcc7ab, 0x6e3569326c784337, |
3924 | | 0xacb92ed9397bf996, 0x49c2c37f07965404, |
3925 | | 0xd7e77a8f87daf7fb, 0xdc33745ec97be906, |
3926 | | 0x86f0ac99b4e8dafd, 0x69a028bb3ded71a3, |
3927 | | 0xa8acd7c0222311bc, 0xc40832ea0d68ce0c, |
3928 | | 0xd2d80db02aabd62b, 0xf50a3fa490c30190, |
3929 | | 0x83c7088e1aab65db, 0x792667c6da79e0fa, |
3930 | | 0xa4b8cab1a1563f52, 0x577001b891185938, |
3931 | | 0xcde6fd5e09abcf26, 0xed4c0226b55e6f86, |
3932 | | 0x80b05e5ac60b6178, 0x544f8158315b05b4, |
3933 | | 0xa0dc75f1778e39d6, 0x696361ae3db1c721, |
3934 | | 0xc913936dd571c84c, 0x3bc3a19cd1e38e9, |
3935 | | 0xfb5878494ace3a5f, 0x4ab48a04065c723, |
3936 | | 0x9d174b2dcec0e47b, 0x62eb0d64283f9c76, |
3937 | | 0xc45d1df942711d9a, 0x3ba5d0bd324f8394, |
3938 | | 0xf5746577930d6500, 0xca8f44ec7ee36479, |
3939 | | 0x9968bf6abbe85f20, 0x7e998b13cf4e1ecb, |
3940 | | 0xbfc2ef456ae276e8, 0x9e3fedd8c321a67e, |
3941 | | 0xefb3ab16c59b14a2, 0xc5cfe94ef3ea101e, |
3942 | | 0x95d04aee3b80ece5, 0xbba1f1d158724a12, |
3943 | | 0xbb445da9ca61281f, 0x2a8a6e45ae8edc97, |
3944 | | 0xea1575143cf97226, 0xf52d09d71a3293bd, |
3945 | | 0x924d692ca61be758, 0x593c2626705f9c56, |
3946 | | 0xb6e0c377cfa2e12e, 0x6f8b2fb00c77836c, |
3947 | | 0xe498f455c38b997a, 0xb6dfb9c0f956447, |
3948 | | 0x8edf98b59a373fec, 0x4724bd4189bd5eac, |
3949 | | 0xb2977ee300c50fe7, 0x58edec91ec2cb657, |
3950 | | 0xdf3d5e9bc0f653e1, 0x2f2967b66737e3ed, |
3951 | | 0x8b865b215899f46c, 0xbd79e0d20082ee74, |
3952 | | 0xae67f1e9aec07187, 0xecd8590680a3aa11, |
3953 | | 0xda01ee641a708de9, 0xe80e6f4820cc9495, |
3954 | | 0x884134fe908658b2, 0x3109058d147fdcdd, |
3955 | | 0xaa51823e34a7eede, 0xbd4b46f0599fd415, |
3956 | | 0xd4e5e2cdc1d1ea96, 0x6c9e18ac7007c91a, |
3957 | | 0x850fadc09923329e, 0x3e2cf6bc604ddb0, |
3958 | | 0xa6539930bf6bff45, 0x84db8346b786151c, |
3959 | | 0xcfe87f7cef46ff16, 0xe612641865679a63, |
3960 | | 0x81f14fae158c5f6e, 0x4fcb7e8f3f60c07e, |
3961 | | 0xa26da3999aef7749, 0xe3be5e330f38f09d, |
3962 | | 0xcb090c8001ab551c, 0x5cadf5bfd3072cc5, |
3963 | | 0xfdcb4fa002162a63, 0x73d9732fc7c8f7f6, |
3964 | | 0x9e9f11c4014dda7e, 0x2867e7fddcdd9afa, |
3965 | | 0xc646d63501a1511d, 0xb281e1fd541501b8, |
3966 | | 0xf7d88bc24209a565, 0x1f225a7ca91a4226, |
3967 | | 0x9ae757596946075f, 0x3375788de9b06958, |
3968 | | 0xc1a12d2fc3978937, 0x52d6b1641c83ae, |
3969 | | 0xf209787bb47d6b84, 0xc0678c5dbd23a49a, |
3970 | | 0x9745eb4d50ce6332, 0xf840b7ba963646e0, |
3971 | | 0xbd176620a501fbff, 0xb650e5a93bc3d898, |
3972 | | 0xec5d3fa8ce427aff, 0xa3e51f138ab4cebe, |
3973 | | 0x93ba47c980e98cdf, 0xc66f336c36b10137, |
3974 | | 0xb8a8d9bbe123f017, 0xb80b0047445d4184, |
3975 | | 0xe6d3102ad96cec1d, 0xa60dc059157491e5, |
3976 | | 0x9043ea1ac7e41392, 0x87c89837ad68db2f, |
3977 | | 0xb454e4a179dd1877, 0x29babe4598c311fb, |
3978 | | 0xe16a1dc9d8545e94, 0xf4296dd6fef3d67a, |
3979 | | 0x8ce2529e2734bb1d, 0x1899e4a65f58660c, |
3980 | | 0xb01ae745b101e9e4, 0x5ec05dcff72e7f8f, |
3981 | | 0xdc21a1171d42645d, 0x76707543f4fa1f73, |
3982 | | 0x899504ae72497eba, 0x6a06494a791c53a8, |
3983 | | 0xabfa45da0edbde69, 0x487db9d17636892, |
3984 | | 0xd6f8d7509292d603, 0x45a9d2845d3c42b6, |
3985 | | 0x865b86925b9bc5c2, 0xb8a2392ba45a9b2, |
3986 | | 0xa7f26836f282b732, 0x8e6cac7768d7141e, |
3987 | | 0xd1ef0244af2364ff, 0x3207d795430cd926, |
3988 | | 0x8335616aed761f1f, 0x7f44e6bd49e807b8, |
3989 | | 0xa402b9c5a8d3a6e7, 0x5f16206c9c6209a6, |
3990 | | 0xcd036837130890a1, 0x36dba887c37a8c0f, |
3991 | | 0x802221226be55a64, 0xc2494954da2c9789, |
3992 | | 0xa02aa96b06deb0fd, 0xf2db9baa10b7bd6c, |
3993 | | 0xc83553c5c8965d3d, 0x6f92829494e5acc7, |
3994 | | 0xfa42a8b73abbf48c, 0xcb772339ba1f17f9, |
3995 | | 0x9c69a97284b578d7, 0xff2a760414536efb, |
3996 | | 0xc38413cf25e2d70d, 0xfef5138519684aba, |
3997 | | 0xf46518c2ef5b8cd1, 0x7eb258665fc25d69, |
3998 | | 0x98bf2f79d5993802, 0xef2f773ffbd97a61, |
3999 | | 0xbeeefb584aff8603, 0xaafb550ffacfd8fa, |
4000 | | 0xeeaaba2e5dbf6784, 0x95ba2a53f983cf38, |
4001 | | 0x952ab45cfa97a0b2, 0xdd945a747bf26183, |
4002 | | 0xba756174393d88df, 0x94f971119aeef9e4, |
4003 | | 0xe912b9d1478ceb17, 0x7a37cd5601aab85d, |
4004 | | 0x91abb422ccb812ee, 0xac62e055c10ab33a, |
4005 | | 0xb616a12b7fe617aa, 0x577b986b314d6009, |
4006 | | 0xe39c49765fdf9d94, 0xed5a7e85fda0b80b, |
4007 | | 0x8e41ade9fbebc27d, 0x14588f13be847307, |
4008 | | 0xb1d219647ae6b31c, 0x596eb2d8ae258fc8, |
4009 | | 0xde469fbd99a05fe3, 0x6fca5f8ed9aef3bb, |
4010 | | 0x8aec23d680043bee, 0x25de7bb9480d5854, |
4011 | | 0xada72ccc20054ae9, 0xaf561aa79a10ae6a, |
4012 | | 0xd910f7ff28069da4, 0x1b2ba1518094da04, |
4013 | | 0x87aa9aff79042286, 0x90fb44d2f05d0842, |
4014 | | 0xa99541bf57452b28, 0x353a1607ac744a53, |
4015 | | 0xd3fa922f2d1675f2, 0x42889b8997915ce8, |
4016 | | 0x847c9b5d7c2e09b7, 0x69956135febada11, |
4017 | | 0xa59bc234db398c25, 0x43fab9837e699095, |
4018 | | 0xcf02b2c21207ef2e, 0x94f967e45e03f4bb, |
4019 | | 0x8161afb94b44f57d, 0x1d1be0eebac278f5, |
4020 | | 0xa1ba1ba79e1632dc, 0x6462d92a69731732, |
4021 | | 0xca28a291859bbf93, 0x7d7b8f7503cfdcfe, |
4022 | | 0xfcb2cb35e702af78, 0x5cda735244c3d43e, |
4023 | | 0x9defbf01b061adab, 0x3a0888136afa64a7, |
4024 | | 0xc56baec21c7a1916, 0x88aaa1845b8fdd0, |
4025 | | 0xf6c69a72a3989f5b, 0x8aad549e57273d45, |
4026 | | 0x9a3c2087a63f6399, 0x36ac54e2f678864b, |
4027 | | 0xc0cb28a98fcf3c7f, 0x84576a1bb416a7dd, |
4028 | | 0xf0fdf2d3f3c30b9f, 0x656d44a2a11c51d5, |
4029 | | 0x969eb7c47859e743, 0x9f644ae5a4b1b325, |
4030 | | 0xbc4665b596706114, 0x873d5d9f0dde1fee, |
4031 | | 0xeb57ff22fc0c7959, 0xa90cb506d155a7ea, |
4032 | | 0x9316ff75dd87cbd8, 0x9a7f12442d588f2, |
4033 | | 0xb7dcbf5354e9bece, 0xc11ed6d538aeb2f, |
4034 | | 0xe5d3ef282a242e81, 0x8f1668c8a86da5fa, |
4035 | | 0x8fa475791a569d10, 0xf96e017d694487bc, |
4036 | | 0xb38d92d760ec4455, 0x37c981dcc395a9ac, |
4037 | | 0xe070f78d3927556a, 0x85bbe253f47b1417, |
4038 | | 0x8c469ab843b89562, 0x93956d7478ccec8e, |
4039 | | 0xaf58416654a6babb, 0x387ac8d1970027b2, |
4040 | | 0xdb2e51bfe9d0696a, 0x6997b05fcc0319e, |
4041 | | 0x88fcf317f22241e2, 0x441fece3bdf81f03, |
4042 | | 0xab3c2fddeeaad25a, 0xd527e81cad7626c3, |
4043 | | 0xd60b3bd56a5586f1, 0x8a71e223d8d3b074, |
4044 | | 0x85c7056562757456, 0xf6872d5667844e49, |
4045 | | 0xa738c6bebb12d16c, 0xb428f8ac016561db, |
4046 | | 0xd106f86e69d785c7, 0xe13336d701beba52, |
4047 | | 0x82a45b450226b39c, 0xecc0024661173473, |
4048 | | 0xa34d721642b06084, 0x27f002d7f95d0190, |
4049 | | 0xcc20ce9bd35c78a5, 0x31ec038df7b441f4, |
4050 | | 0xff290242c83396ce, 0x7e67047175a15271, |
4051 | | 0x9f79a169bd203e41, 0xf0062c6e984d386, |
4052 | | 0xc75809c42c684dd1, 0x52c07b78a3e60868, |
4053 | | 0xf92e0c3537826145, 0xa7709a56ccdf8a82, |
4054 | | 0x9bbcc7a142b17ccb, 0x88a66076400bb691, |
4055 | | 0xc2abf989935ddbfe, 0x6acff893d00ea435, |
4056 | | 0xf356f7ebf83552fe, 0x583f6b8c4124d43, |
4057 | | 0x98165af37b2153de, 0xc3727a337a8b704a, |
4058 | | 0xbe1bf1b059e9a8d6, 0x744f18c0592e4c5c, |
4059 | | 0xeda2ee1c7064130c, 0x1162def06f79df73, |
4060 | | 0x9485d4d1c63e8be7, 0x8addcb5645ac2ba8, |
4061 | | 0xb9a74a0637ce2ee1, 0x6d953e2bd7173692, |
4062 | | 0xe8111c87c5c1ba99, 0xc8fa8db6ccdd0437, |
4063 | | 0x910ab1d4db9914a0, 0x1d9c9892400a22a2, |
4064 | | 0xb54d5e4a127f59c8, 0x2503beb6d00cab4b, |
4065 | | 0xe2a0b5dc971f303a, 0x2e44ae64840fd61d, |
4066 | | 0x8da471a9de737e24, 0x5ceaecfed289e5d2, |
4067 | | 0xb10d8e1456105dad, 0x7425a83e872c5f47, |
4068 | | 0xdd50f1996b947518, 0xd12f124e28f77719, |
4069 | | 0x8a5296ffe33cc92f, 0x82bd6b70d99aaa6f, |
4070 | | 0xace73cbfdc0bfb7b, 0x636cc64d1001550b, |
4071 | | 0xd8210befd30efa5a, 0x3c47f7e05401aa4e, |
4072 | | 0x8714a775e3e95c78, 0x65acfaec34810a71, |
4073 | | 0xa8d9d1535ce3b396, 0x7f1839a741a14d0d, |
4074 | | 0xd31045a8341ca07c, 0x1ede48111209a050, |
4075 | | 0x83ea2b892091e44d, 0x934aed0aab460432, |
4076 | | 0xa4e4b66b68b65d60, 0xf81da84d5617853f, |
4077 | | 0xce1de40642e3f4b9, 0x36251260ab9d668e, |
4078 | | 0x80d2ae83e9ce78f3, 0xc1d72b7c6b426019, |
4079 | | 0xa1075a24e4421730, 0xb24cf65b8612f81f, |
4080 | | 0xc94930ae1d529cfc, 0xdee033f26797b627, |
4081 | | 0xfb9b7cd9a4a7443c, 0x169840ef017da3b1, |
4082 | | 0x9d412e0806e88aa5, 0x8e1f289560ee864e, |
4083 | | 0xc491798a08a2ad4e, 0xf1a6f2bab92a27e2, |
4084 | | 0xf5b5d7ec8acb58a2, 0xae10af696774b1db, |
4085 | | 0x9991a6f3d6bf1765, 0xacca6da1e0a8ef29, |
4086 | | 0xbff610b0cc6edd3f, 0x17fd090a58d32af3, |
4087 | | 0xeff394dcff8a948e, 0xddfc4b4cef07f5b0, |
4088 | | 0x95f83d0a1fb69cd9, 0x4abdaf101564f98e, |
4089 | | 0xbb764c4ca7a4440f, 0x9d6d1ad41abe37f1, |
4090 | | 0xea53df5fd18d5513, 0x84c86189216dc5ed, |
4091 | | 0x92746b9be2f8552c, 0x32fd3cf5b4e49bb4, |
4092 | | 0xb7118682dbb66a77, 0x3fbc8c33221dc2a1, |
4093 | | 0xe4d5e82392a40515, 0xfabaf3feaa5334a, |
4094 | | 0x8f05b1163ba6832d, 0x29cb4d87f2a7400e, |
4095 | | 0xb2c71d5bca9023f8, 0x743e20e9ef511012, |
4096 | | 0xdf78e4b2bd342cf6, 0x914da9246b255416, |
4097 | | 0x8bab8eefb6409c1a, 0x1ad089b6c2f7548e, |
4098 | | 0xae9672aba3d0c320, 0xa184ac2473b529b1, |
4099 | | 0xda3c0f568cc4f3e8, 0xc9e5d72d90a2741e, |
4100 | | 0x8865899617fb1871, 0x7e2fa67c7a658892, |
4101 | | 0xaa7eebfb9df9de8d, 0xddbb901b98feeab7, |
4102 | | 0xd51ea6fa85785631, 0x552a74227f3ea565, |
4103 | | 0x8533285c936b35de, 0xd53a88958f87275f, |
4104 | | 0xa67ff273b8460356, 0x8a892abaf368f137, |
4105 | | 0xd01fef10a657842c, 0x2d2b7569b0432d85, |
4106 | | 0x8213f56a67f6b29b, 0x9c3b29620e29fc73, |
4107 | | 0xa298f2c501f45f42, 0x8349f3ba91b47b8f, |
4108 | | 0xcb3f2f7642717713, 0x241c70a936219a73, |
4109 | | 0xfe0efb53d30dd4d7, 0xed238cd383aa0110, |
4110 | | 0x9ec95d1463e8a506, 0xf4363804324a40aa, |
4111 | | 0xc67bb4597ce2ce48, 0xb143c6053edcd0d5, |
4112 | | 0xf81aa16fdc1b81da, 0xdd94b7868e94050a, |
4113 | | 0x9b10a4e5e9913128, 0xca7cf2b4191c8326, |
4114 | | 0xc1d4ce1f63f57d72, 0xfd1c2f611f63a3f0, |
4115 | | 0xf24a01a73cf2dccf, 0xbc633b39673c8cec, |
4116 | | 0x976e41088617ca01, 0xd5be0503e085d813, |
4117 | | 0xbd49d14aa79dbc82, 0x4b2d8644d8a74e18, |
4118 | | 0xec9c459d51852ba2, 0xddf8e7d60ed1219e, |
4119 | | 0x93e1ab8252f33b45, 0xcabb90e5c942b503, |
4120 | | 0xb8da1662e7b00a17, 0x3d6a751f3b936243, |
4121 | | 0xe7109bfba19c0c9d, 0xcc512670a783ad4, |
4122 | | 0x906a617d450187e2, 0x27fb2b80668b24c5, |
4123 | | 0xb484f9dc9641e9da, 0xb1f9f660802dedf6, |
4124 | | 0xe1a63853bbd26451, 0x5e7873f8a0396973, |
4125 | | 0x8d07e33455637eb2, 0xdb0b487b6423e1e8, |
4126 | | 0xb049dc016abc5e5f, 0x91ce1a9a3d2cda62, |
4127 | | 0xdc5c5301c56b75f7, 0x7641a140cc7810fb, |
4128 | | 0x89b9b3e11b6329ba, 0xa9e904c87fcb0a9d, |
4129 | | 0xac2820d9623bf429, 0x546345fa9fbdcd44, |
4130 | | 0xd732290fbacaf133, 0xa97c177947ad4095, |
4131 | | 0x867f59a9d4bed6c0, 0x49ed8eabcccc485d, |
4132 | | 0xa81f301449ee8c70, 0x5c68f256bfff5a74, |
4133 | | 0xd226fc195c6a2f8c, 0x73832eec6fff3111, |
4134 | | 0x83585d8fd9c25db7, 0xc831fd53c5ff7eab, |
4135 | | 0xa42e74f3d032f525, 0xba3e7ca8b77f5e55, |
4136 | | 0xcd3a1230c43fb26f, 0x28ce1bd2e55f35eb, |
4137 | | 0x80444b5e7aa7cf85, 0x7980d163cf5b81b3, |
4138 | | 0xa0555e361951c366, 0xd7e105bcc332621f, |
4139 | | 0xc86ab5c39fa63440, 0x8dd9472bf3fefaa7, |
4140 | | 0xfa856334878fc150, 0xb14f98f6f0feb951, |
4141 | | 0x9c935e00d4b9d8d2, 0x6ed1bf9a569f33d3, |
4142 | | 0xc3b8358109e84f07, 0xa862f80ec4700c8, |
4143 | | 0xf4a642e14c6262c8, 0xcd27bb612758c0fa, |
4144 | | 0x98e7e9cccfbd7dbd, 0x8038d51cb897789c, |
4145 | | 0xbf21e44003acdd2c, 0xe0470a63e6bd56c3, |
4146 | | 0xeeea5d5004981478, 0x1858ccfce06cac74, |
4147 | | 0x95527a5202df0ccb, 0xf37801e0c43ebc8, |
4148 | | 0xbaa718e68396cffd, 0xd30560258f54e6ba, |
4149 | | 0xe950df20247c83fd, 0x47c6b82ef32a2069, |
4150 | | 0x91d28b7416cdd27e, 0x4cdc331d57fa5441, |
4151 | | 0xb6472e511c81471d, 0xe0133fe4adf8e952, |
4152 | | 0xe3d8f9e563a198e5, 0x58180fddd97723a6, |
4153 | | 0x8e679c2f5e44ff8f, 0x570f09eaa7ea7648, |
4154 | | }; |
4155 | | }; |
4156 | | |
4157 | | #if FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE |
4158 | | |
4159 | | template <class unused> |
4160 | | constexpr uint64_t |
4161 | | powers_template<unused>::power_of_five_128[number_of_entries]; |
4162 | | |
4163 | | #endif |
4164 | | |
4165 | | using powers = powers_template<>; |
4166 | | |
4167 | | } // namespace fast_float |
4168 | | |
4169 | | #endif |
4170 | | |
4171 | | #ifndef FASTFLOAT_DECIMAL_TO_BINARY_H |
4172 | | #define FASTFLOAT_DECIMAL_TO_BINARY_H |
4173 | | |
4174 | | #include <cmath> |
4175 | | #include <cstdlib> |
4176 | | #include <cstring> |
4177 | | |
4178 | | namespace fast_float { |
4179 | | |
4180 | | // This will compute or rather approximate w * 5**q and return a pair of 64-bit |
4181 | | // words approximating the result, with the "high" part corresponding to the |
4182 | | // most significant bits and the low part corresponding to the least significant |
4183 | | // bits. |
4184 | | // |
4185 | | template <int bit_precision> |
4186 | | fastfloat_really_inline FASTFLOAT_CONSTEXPR20 value128 |
4187 | 171k | compute_product_approximation(int64_t q, uint64_t w) { |
4188 | 171k | int const index = 2 * int(q - powers::smallest_power_of_five); |
4189 | | // For small values of q, e.g., q in [0,27], the answer is always exact |
4190 | | // because The line value128 firstproduct = full_multiplication(w, |
4191 | | // power_of_five_128[index]); gives the exact answer. |
4192 | 171k | value128 firstproduct = |
4193 | 171k | full_multiplication(w, powers::power_of_five_128[index]); |
4194 | 171k | static_assert((bit_precision >= 0) && (bit_precision <= 64), |
4195 | 171k | " precision should be in (0,64]"); |
4196 | 171k | constexpr uint64_t precision_mask = |
4197 | 171k | (bit_precision < 64) ? (uint64_t(0xFFFFFFFFFFFFFFFF) >> bit_precision) |
4198 | 171k | : uint64_t(0xFFFFFFFFFFFFFFFF); |
4199 | 171k | if ((firstproduct.high & precision_mask) == |
4200 | 171k | precision_mask) { // could further guard with (lower + w < lower) |
4201 | | // regarding the second product, we only need secondproduct.high, but our |
4202 | | // expectation is that the compiler will optimize this extra work away if |
4203 | | // needed. |
4204 | 38.0k | value128 secondproduct = |
4205 | 38.0k | full_multiplication(w, powers::power_of_five_128[index + 1]); |
4206 | 38.0k | firstproduct.low += secondproduct.high; |
4207 | 38.0k | if (secondproduct.high > firstproduct.low) { |
4208 | 23.0k | firstproduct.high++; |
4209 | 23.0k | } |
4210 | 38.0k | } |
4211 | 171k | return firstproduct; |
4212 | 171k | } fast_float::value128 fast_float::compute_product_approximation<55>(long, unsigned long) Line | Count | Source | 4187 | 171k | compute_product_approximation(int64_t q, uint64_t w) { | 4188 | 171k | int const index = 2 * int(q - powers::smallest_power_of_five); | 4189 | | // For small values of q, e.g., q in [0,27], the answer is always exact | 4190 | | // because The line value128 firstproduct = full_multiplication(w, | 4191 | | // power_of_five_128[index]); gives the exact answer. | 4192 | 171k | value128 firstproduct = | 4193 | 171k | full_multiplication(w, powers::power_of_five_128[index]); | 4194 | 171k | static_assert((bit_precision >= 0) && (bit_precision <= 64), | 4195 | 171k | " precision should be in (0,64]"); | 4196 | 171k | constexpr uint64_t precision_mask = | 4197 | 171k | (bit_precision < 64) ? (uint64_t(0xFFFFFFFFFFFFFFFF) >> bit_precision) | 4198 | 171k | : uint64_t(0xFFFFFFFFFFFFFFFF); | 4199 | 171k | if ((firstproduct.high & precision_mask) == | 4200 | 171k | precision_mask) { // could further guard with (lower + w < lower) | 4201 | | // regarding the second product, we only need secondproduct.high, but our | 4202 | | // expectation is that the compiler will optimize this extra work away if | 4203 | | // needed. | 4204 | 38.0k | value128 secondproduct = | 4205 | 38.0k | full_multiplication(w, powers::power_of_five_128[index + 1]); | 4206 | 38.0k | firstproduct.low += secondproduct.high; | 4207 | 38.0k | if (secondproduct.high > firstproduct.low) { | 4208 | 23.0k | firstproduct.high++; | 4209 | 23.0k | } | 4210 | 38.0k | } | 4211 | 171k | return firstproduct; | 4212 | 171k | } |
Unexecuted instantiation: fast_float::value128 fast_float::compute_product_approximation<26>(long, unsigned long) |
4213 | | |
4214 | | namespace detail { |
4215 | | /** |
4216 | | * For q in (0,350), we have that |
4217 | | * f = (((152170 + 65536) * q ) >> 16); |
4218 | | * is equal to |
4219 | | * floor(p) + q |
4220 | | * where |
4221 | | * p = log(5**q)/log(2) = q * log(5)/log(2) |
4222 | | * |
4223 | | * For negative values of q in (-400,0), we have that |
4224 | | * f = (((152170 + 65536) * q ) >> 16); |
4225 | | * is equal to |
4226 | | * -ceil(p) + q |
4227 | | * where |
4228 | | * p = log(5**-q)/log(2) = -q * log(5)/log(2) |
4229 | | */ |
4230 | 171k | constexpr fastfloat_really_inline int32_t power(int32_t q) noexcept { |
4231 | 171k | return (((152170 + 65536) * q) >> 16) + 63; |
4232 | 171k | } |
4233 | | } // namespace detail |
4234 | | |
4235 | | // create an adjusted mantissa, biased by the invalid power2 |
4236 | | // for significant digits already multiplied by 10 ** q. |
4237 | | template <typename binary> |
4238 | | fastfloat_really_inline FASTFLOAT_CONSTEXPR14 adjusted_mantissa |
4239 | 35.1k | compute_error_scaled(int64_t q, uint64_t w, int lz) noexcept { |
4240 | 35.1k | int hilz = int(w >> 63) ^ 1; |
4241 | 35.1k | adjusted_mantissa answer; |
4242 | 35.1k | answer.mantissa = w << hilz; |
4243 | 35.1k | int bias = binary::mantissa_explicit_bits() - binary::minimum_exponent(); |
4244 | 35.1k | answer.power2 = int32_t(detail::power(int32_t(q)) + bias - hilz - lz - 62 + |
4245 | 35.1k | invalid_am_bias); |
4246 | 35.1k | return answer; |
4247 | 35.1k | } fast_float::adjusted_mantissa fast_float::compute_error_scaled<fast_float::binary_format<double> >(long, unsigned long, int) Line | Count | Source | 4239 | 35.1k | compute_error_scaled(int64_t q, uint64_t w, int lz) noexcept { | 4240 | 35.1k | int hilz = int(w >> 63) ^ 1; | 4241 | 35.1k | adjusted_mantissa answer; | 4242 | 35.1k | answer.mantissa = w << hilz; | 4243 | 35.1k | int bias = binary::mantissa_explicit_bits() - binary::minimum_exponent(); | 4244 | 35.1k | answer.power2 = int32_t(detail::power(int32_t(q)) + bias - hilz - lz - 62 + | 4245 | 35.1k | invalid_am_bias); | 4246 | 35.1k | return answer; | 4247 | 35.1k | } |
Unexecuted instantiation: fast_float::adjusted_mantissa fast_float::compute_error_scaled<fast_float::binary_format<float> >(long, unsigned long, int) |
4248 | | |
4249 | | // w * 10 ** q, without rounding the representation up. |
4250 | | // the power2 in the exponent will be adjusted by invalid_am_bias. |
4251 | | template <typename binary> |
4252 | | fastfloat_really_inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa |
4253 | 35.1k | compute_error(int64_t q, uint64_t w) noexcept { |
4254 | 35.1k | int lz = leading_zeroes(w); |
4255 | 35.1k | w <<= lz; |
4256 | 35.1k | value128 product = |
4257 | 35.1k | compute_product_approximation<binary::mantissa_explicit_bits() + 3>(q, w); |
4258 | 35.1k | return compute_error_scaled<binary>(q, product.high, lz); |
4259 | 35.1k | } fast_float::adjusted_mantissa fast_float::compute_error<fast_float::binary_format<double> >(long, unsigned long) Line | Count | Source | 4253 | 35.1k | compute_error(int64_t q, uint64_t w) noexcept { | 4254 | 35.1k | int lz = leading_zeroes(w); | 4255 | 35.1k | w <<= lz; | 4256 | 35.1k | value128 product = | 4257 | 35.1k | compute_product_approximation<binary::mantissa_explicit_bits() + 3>(q, w); | 4258 | 35.1k | return compute_error_scaled<binary>(q, product.high, lz); | 4259 | 35.1k | } |
Unexecuted instantiation: fast_float::adjusted_mantissa fast_float::compute_error<fast_float::binary_format<float> >(long, unsigned long) |
4260 | | |
4261 | | // Computers w * 10 ** q. |
4262 | | // The returned value should be a valid number that simply needs to be |
4263 | | // packed. However, in some very rare cases, the computation will fail. In such |
4264 | | // cases, we return an adjusted_mantissa with a negative power of 2: the caller |
4265 | | // should recompute in such cases. |
4266 | | template <typename binary> |
4267 | | fastfloat_really_inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa |
4268 | 143k | compute_float(int64_t q, uint64_t w) noexcept { |
4269 | 143k | adjusted_mantissa answer; |
4270 | 143k | if ((w == 0) || (q < binary::smallest_power_of_ten())) { |
4271 | 2.41k | answer.power2 = 0; |
4272 | 2.41k | answer.mantissa = 0; |
4273 | | // result should be zero |
4274 | 2.41k | return answer; |
4275 | 2.41k | } |
4276 | 141k | if (q > binary::largest_power_of_ten()) { |
4277 | | // we want to get infinity: |
4278 | 4.80k | answer.power2 = binary::infinite_power(); |
4279 | 4.80k | answer.mantissa = 0; |
4280 | 4.80k | return answer; |
4281 | 4.80k | } |
4282 | | // At this point in time q is in [powers::smallest_power_of_five, |
4283 | | // powers::largest_power_of_five]. |
4284 | | |
4285 | | // We want the most significant bit of i to be 1. Shift if needed. |
4286 | 136k | int lz = leading_zeroes(w); |
4287 | 136k | w <<= lz; |
4288 | | |
4289 | | // The required precision is binary::mantissa_explicit_bits() + 3 because |
4290 | | // 1. We need the implicit bit |
4291 | | // 2. We need an extra bit for rounding purposes |
4292 | | // 3. We might lose a bit due to the "upperbit" routine (result too small, |
4293 | | // requiring a shift) |
4294 | | |
4295 | 136k | value128 product = |
4296 | 136k | compute_product_approximation<binary::mantissa_explicit_bits() + 3>(q, w); |
4297 | | // The computed 'product' is always sufficient. |
4298 | | // Mathematical proof: |
4299 | | // Noble Mushtak and Daniel Lemire, Fast Number Parsing Without Fallback (to |
4300 | | // appear) See script/mushtak_lemire.py |
4301 | | |
4302 | | // The "compute_product_approximation" function can be slightly slower than a |
4303 | | // branchless approach: value128 product = compute_product(q, w); but in |
4304 | | // practice, we can win big with the compute_product_approximation if its |
4305 | | // additional branch is easily predicted. Which is best is data specific. |
4306 | 136k | int upperbit = int(product.high >> 63); |
4307 | 136k | int shift = upperbit + 64 - binary::mantissa_explicit_bits() - 3; |
4308 | | |
4309 | 136k | answer.mantissa = product.high >> shift; |
4310 | | |
4311 | 136k | answer.power2 = int32_t(detail::power(int32_t(q)) + upperbit - lz - |
4312 | 136k | binary::minimum_exponent()); |
4313 | 136k | if (answer.power2 <= 0) { // we have a subnormal? |
4314 | | // Here have that answer.power2 <= 0 so -answer.power2 >= 0 |
4315 | 6.75k | if (-answer.power2 + 1 >= |
4316 | 6.75k | 64) { // if we have more than 64 bits below the minimum exponent, you |
4317 | | // have a zero for sure. |
4318 | 477 | answer.power2 = 0; |
4319 | 477 | answer.mantissa = 0; |
4320 | | // result should be zero |
4321 | 477 | return answer; |
4322 | 477 | } |
4323 | | // next line is safe because -answer.power2 + 1 < 64 |
4324 | 6.28k | answer.mantissa >>= -answer.power2 + 1; |
4325 | | // Thankfully, we can't have both "round-to-even" and subnormals because |
4326 | | // "round-to-even" only occurs for powers close to 0 in the 32-bit and |
4327 | | // and 64-bit case (with no more than 19 digits). |
4328 | 6.28k | answer.mantissa += (answer.mantissa & 1); // round up |
4329 | 6.28k | answer.mantissa >>= 1; |
4330 | | // There is a weird scenario where we don't have a subnormal but just. |
4331 | | // Suppose we start with 2.2250738585072013e-308, we end up |
4332 | | // with 0x3fffffffffffff x 2^-1023-53 which is technically subnormal |
4333 | | // whereas 0x40000000000000 x 2^-1023-53 is normal. Now, we need to round |
4334 | | // up 0x3fffffffffffff x 2^-1023-53 and once we do, we are no longer |
4335 | | // subnormal, but we can only know this after rounding. |
4336 | | // So we only declare a subnormal if we are smaller than the threshold. |
4337 | 6.28k | answer.power2 = |
4338 | 6.28k | (answer.mantissa < (uint64_t(1) << binary::mantissa_explicit_bits())) |
4339 | 6.28k | ? 0 |
4340 | 6.28k | : 1; |
4341 | 6.28k | return answer; |
4342 | 6.75k | } |
4343 | | |
4344 | | // usually, we round *up*, but if we fall right in between and and we have an |
4345 | | // even basis, we need to round down |
4346 | | // We are only concerned with the cases where 5**q fits in single 64-bit word. |
4347 | 129k | if ((product.low <= 1) && (q >= binary::min_exponent_round_to_even()) && |
4348 | 17.5k | (q <= binary::max_exponent_round_to_even()) && |
4349 | 17.2k | ((answer.mantissa & 3) == 1)) { // we may fall between two floats! |
4350 | | // To be in-between two floats we need that in doing |
4351 | | // answer.mantissa = product.high >> (upperbit + 64 - |
4352 | | // binary::mantissa_explicit_bits() - 3); |
4353 | | // ... we dropped out only zeroes. But if this happened, then we can go |
4354 | | // back!!! |
4355 | 12.2k | if ((answer.mantissa << shift) == product.high) { |
4356 | 7.08k | answer.mantissa &= ~uint64_t(1); // flip it so that we do not round up |
4357 | 7.08k | } |
4358 | 12.2k | } |
4359 | | |
4360 | 129k | answer.mantissa += (answer.mantissa & 1); // round up |
4361 | 129k | answer.mantissa >>= 1; |
4362 | 129k | if (answer.mantissa >= (uint64_t(2) << binary::mantissa_explicit_bits())) { |
4363 | 4.05k | answer.mantissa = (uint64_t(1) << binary::mantissa_explicit_bits()); |
4364 | 4.05k | answer.power2++; // undo previous addition |
4365 | 4.05k | } |
4366 | | |
4367 | 129k | answer.mantissa &= ~(uint64_t(1) << binary::mantissa_explicit_bits()); |
4368 | 129k | if (answer.power2 >= binary::infinite_power()) { // infinity |
4369 | 2.13k | answer.power2 = binary::infinite_power(); |
4370 | 2.13k | answer.mantissa = 0; |
4371 | 2.13k | } |
4372 | 129k | return answer; |
4373 | 136k | } fast_float::adjusted_mantissa fast_float::compute_float<fast_float::binary_format<double> >(long, unsigned long) Line | Count | Source | 4268 | 143k | compute_float(int64_t q, uint64_t w) noexcept { | 4269 | 143k | adjusted_mantissa answer; | 4270 | 143k | if ((w == 0) || (q < binary::smallest_power_of_ten())) { | 4271 | 2.41k | answer.power2 = 0; | 4272 | 2.41k | answer.mantissa = 0; | 4273 | | // result should be zero | 4274 | 2.41k | return answer; | 4275 | 2.41k | } | 4276 | 141k | if (q > binary::largest_power_of_ten()) { | 4277 | | // we want to get infinity: | 4278 | 4.80k | answer.power2 = binary::infinite_power(); | 4279 | 4.80k | answer.mantissa = 0; | 4280 | 4.80k | return answer; | 4281 | 4.80k | } | 4282 | | // At this point in time q is in [powers::smallest_power_of_five, | 4283 | | // powers::largest_power_of_five]. | 4284 | | | 4285 | | // We want the most significant bit of i to be 1. Shift if needed. | 4286 | 136k | int lz = leading_zeroes(w); | 4287 | 136k | w <<= lz; | 4288 | | | 4289 | | // The required precision is binary::mantissa_explicit_bits() + 3 because | 4290 | | // 1. We need the implicit bit | 4291 | | // 2. We need an extra bit for rounding purposes | 4292 | | // 3. We might lose a bit due to the "upperbit" routine (result too small, | 4293 | | // requiring a shift) | 4294 | | | 4295 | 136k | value128 product = | 4296 | 136k | compute_product_approximation<binary::mantissa_explicit_bits() + 3>(q, w); | 4297 | | // The computed 'product' is always sufficient. | 4298 | | // Mathematical proof: | 4299 | | // Noble Mushtak and Daniel Lemire, Fast Number Parsing Without Fallback (to | 4300 | | // appear) See script/mushtak_lemire.py | 4301 | | | 4302 | | // The "compute_product_approximation" function can be slightly slower than a | 4303 | | // branchless approach: value128 product = compute_product(q, w); but in | 4304 | | // practice, we can win big with the compute_product_approximation if its | 4305 | | // additional branch is easily predicted. Which is best is data specific. | 4306 | 136k | int upperbit = int(product.high >> 63); | 4307 | 136k | int shift = upperbit + 64 - binary::mantissa_explicit_bits() - 3; | 4308 | | | 4309 | 136k | answer.mantissa = product.high >> shift; | 4310 | | | 4311 | 136k | answer.power2 = int32_t(detail::power(int32_t(q)) + upperbit - lz - | 4312 | 136k | binary::minimum_exponent()); | 4313 | 136k | if (answer.power2 <= 0) { // we have a subnormal? | 4314 | | // Here have that answer.power2 <= 0 so -answer.power2 >= 0 | 4315 | 6.75k | if (-answer.power2 + 1 >= | 4316 | 6.75k | 64) { // if we have more than 64 bits below the minimum exponent, you | 4317 | | // have a zero for sure. | 4318 | 477 | answer.power2 = 0; | 4319 | 477 | answer.mantissa = 0; | 4320 | | // result should be zero | 4321 | 477 | return answer; | 4322 | 477 | } | 4323 | | // next line is safe because -answer.power2 + 1 < 64 | 4324 | 6.28k | answer.mantissa >>= -answer.power2 + 1; | 4325 | | // Thankfully, we can't have both "round-to-even" and subnormals because | 4326 | | // "round-to-even" only occurs for powers close to 0 in the 32-bit and | 4327 | | // and 64-bit case (with no more than 19 digits). | 4328 | 6.28k | answer.mantissa += (answer.mantissa & 1); // round up | 4329 | 6.28k | answer.mantissa >>= 1; | 4330 | | // There is a weird scenario where we don't have a subnormal but just. | 4331 | | // Suppose we start with 2.2250738585072013e-308, we end up | 4332 | | // with 0x3fffffffffffff x 2^-1023-53 which is technically subnormal | 4333 | | // whereas 0x40000000000000 x 2^-1023-53 is normal. Now, we need to round | 4334 | | // up 0x3fffffffffffff x 2^-1023-53 and once we do, we are no longer | 4335 | | // subnormal, but we can only know this after rounding. | 4336 | | // So we only declare a subnormal if we are smaller than the threshold. | 4337 | 6.28k | answer.power2 = | 4338 | 6.28k | (answer.mantissa < (uint64_t(1) << binary::mantissa_explicit_bits())) | 4339 | 6.28k | ? 0 | 4340 | 6.28k | : 1; | 4341 | 6.28k | return answer; | 4342 | 6.75k | } | 4343 | | | 4344 | | // usually, we round *up*, but if we fall right in between and and we have an | 4345 | | // even basis, we need to round down | 4346 | | // We are only concerned with the cases where 5**q fits in single 64-bit word. | 4347 | 129k | if ((product.low <= 1) && (q >= binary::min_exponent_round_to_even()) && | 4348 | 17.5k | (q <= binary::max_exponent_round_to_even()) && | 4349 | 17.2k | ((answer.mantissa & 3) == 1)) { // we may fall between two floats! | 4350 | | // To be in-between two floats we need that in doing | 4351 | | // answer.mantissa = product.high >> (upperbit + 64 - | 4352 | | // binary::mantissa_explicit_bits() - 3); | 4353 | | // ... we dropped out only zeroes. But if this happened, then we can go | 4354 | | // back!!! | 4355 | 12.2k | if ((answer.mantissa << shift) == product.high) { | 4356 | 7.08k | answer.mantissa &= ~uint64_t(1); // flip it so that we do not round up | 4357 | 7.08k | } | 4358 | 12.2k | } | 4359 | | | 4360 | 129k | answer.mantissa += (answer.mantissa & 1); // round up | 4361 | 129k | answer.mantissa >>= 1; | 4362 | 129k | if (answer.mantissa >= (uint64_t(2) << binary::mantissa_explicit_bits())) { | 4363 | 4.05k | answer.mantissa = (uint64_t(1) << binary::mantissa_explicit_bits()); | 4364 | 4.05k | answer.power2++; // undo previous addition | 4365 | 4.05k | } | 4366 | | | 4367 | 129k | answer.mantissa &= ~(uint64_t(1) << binary::mantissa_explicit_bits()); | 4368 | 129k | if (answer.power2 >= binary::infinite_power()) { // infinity | 4369 | 2.13k | answer.power2 = binary::infinite_power(); | 4370 | 2.13k | answer.mantissa = 0; | 4371 | 2.13k | } | 4372 | 129k | return answer; | 4373 | 136k | } |
Unexecuted instantiation: fast_float::adjusted_mantissa fast_float::compute_float<fast_float::binary_format<float> >(long, unsigned long) |
4374 | | |
4375 | | } // namespace fast_float |
4376 | | |
4377 | | #endif |
4378 | | |
4379 | | #ifndef FASTFLOAT_BIGINT_H |
4380 | | #define FASTFLOAT_BIGINT_H |
4381 | | |
4382 | | #include <cstring> |
4383 | | |
4384 | | |
4385 | | namespace fast_float { |
4386 | | |
4387 | | // the limb width: we want efficient multiplication of double the bits in |
4388 | | // limb, or for 64-bit limbs, at least 64-bit multiplication where we can |
4389 | | // extract the high and low parts efficiently. this is every 64-bit |
4390 | | // architecture except for sparc, which emulates 128-bit multiplication. |
4391 | | // we might have platforms where `CHAR_BIT` is not 8, so let's avoid |
4392 | | // doing `8 * sizeof(limb)`. |
4393 | | #if defined(FASTFLOAT_64BIT) && !defined(__sparc) |
4394 | | #define FASTFLOAT_64BIT_LIMB 1 |
4395 | | typedef uint64_t limb; |
4396 | | constexpr size_t limb_bits = 64; |
4397 | | #else |
4398 | | #define FASTFLOAT_32BIT_LIMB |
4399 | | typedef uint32_t limb; |
4400 | | constexpr size_t limb_bits = 32; |
4401 | | #endif |
4402 | | |
4403 | | typedef span<limb> limb_span; |
4404 | | |
4405 | | // number of bits in a bigint. this needs to be at least the number |
4406 | | // of bits required to store the largest bigint, which is |
4407 | | // `log2(10**(digits + max_exp))`, or `log2(10**(767 + 342))`, or |
4408 | | // ~3600 bits, so we round to 4000. |
4409 | | constexpr size_t bigint_bits = 4000; |
4410 | | constexpr size_t bigint_limbs = bigint_bits / limb_bits; |
4411 | | |
4412 | | // vector-like type that is allocated on the stack. the entire |
4413 | | // buffer is pre-allocated, and only the length changes. |
4414 | | template <uint16_t size> struct stackvec { |
4415 | | limb data[size]; |
4416 | | // we never need more than 150 limbs |
4417 | | uint16_t length{0}; |
4418 | | |
4419 | 252k | stackvec() = default; |
4420 | | stackvec(stackvec const &) = delete; |
4421 | | stackvec &operator=(stackvec const &) = delete; |
4422 | | stackvec(stackvec &&) = delete; |
4423 | | stackvec &operator=(stackvec &&other) = delete; |
4424 | | |
4425 | | // create stack vector from existing limb span. |
4426 | 48.7k | FASTFLOAT_CONSTEXPR20 stackvec(limb_span s) { |
4427 | 48.7k | FASTFLOAT_ASSERT(try_extend(s)); |
4428 | 48.7k | } |
4429 | | |
4430 | 25.1M | FASTFLOAT_CONSTEXPR14 limb &operator[](size_t index) noexcept { |
4431 | 25.1M | FASTFLOAT_DEBUG_ASSERT(index < length); |
4432 | 25.1M | return data[index]; |
4433 | 25.1M | } |
4434 | | |
4435 | 154k | FASTFLOAT_CONSTEXPR14 const limb &operator[](size_t index) const noexcept { |
4436 | 154k | FASTFLOAT_DEBUG_ASSERT(index < length); |
4437 | 154k | return data[index]; |
4438 | 154k | } |
4439 | | |
4440 | | // index from the end of the container |
4441 | 110k | FASTFLOAT_CONSTEXPR14 const limb &rindex(size_t index) const noexcept { |
4442 | 110k | FASTFLOAT_DEBUG_ASSERT(index < length); |
4443 | 110k | size_t rindex = length - index - 1; |
4444 | 110k | return data[rindex]; |
4445 | 110k | } |
4446 | | |
4447 | | // set the length, without bounds checking. |
4448 | 652k | FASTFLOAT_CONSTEXPR14 void set_len(size_t len) noexcept { |
4449 | 652k | length = uint16_t(len); |
4450 | 652k | } |
4451 | | |
4452 | 13.9M | constexpr size_t len() const noexcept { return length; } |
4453 | | |
4454 | 32.9k | constexpr bool is_empty() const noexcept { return length == 0; } |
4455 | | |
4456 | 1.15M | constexpr size_t capacity() const noexcept { return size; } |
4457 | | |
4458 | | // append item to vector, without bounds checking |
4459 | 723k | FASTFLOAT_CONSTEXPR14 void push_unchecked(limb value) noexcept { |
4460 | 723k | data[length] = value; |
4461 | 723k | length++; |
4462 | 723k | } |
4463 | | |
4464 | | // append item to vector, returning if item was added |
4465 | 701k | FASTFLOAT_CONSTEXPR14 bool try_push(limb value) noexcept { |
4466 | 701k | if (len() < capacity()) { |
4467 | 701k | push_unchecked(value); |
4468 | 701k | return true; |
4469 | 701k | } else { |
4470 | 0 | return false; |
4471 | 0 | } |
4472 | 701k | } |
4473 | | |
4474 | | // add items to the vector, from a span, without bounds checking |
4475 | 243k | FASTFLOAT_CONSTEXPR20 void extend_unchecked(limb_span s) noexcept { |
4476 | 243k | limb *ptr = data + length; |
4477 | 243k | tinyobj_ff::copy_n(s.ptr, s.len(), ptr); |
4478 | 243k | set_len(len() + s.len()); |
4479 | 243k | } |
4480 | | |
4481 | | // try to add items to the vector, returning if items were added |
4482 | 243k | FASTFLOAT_CONSTEXPR20 bool try_extend(limb_span s) noexcept { |
4483 | 243k | if (len() + s.len() <= capacity()) { |
4484 | 243k | extend_unchecked(s); |
4485 | 243k | return true; |
4486 | 243k | } else { |
4487 | 0 | return false; |
4488 | 0 | } |
4489 | 243k | } |
4490 | | |
4491 | | // resize the vector, without bounds checking |
4492 | | // if the new size is longer than the vector, assign value to each |
4493 | | // appended item. |
4494 | | FASTFLOAT_CONSTEXPR20 |
4495 | 193k | void resize_unchecked(size_t new_len, limb value) noexcept { |
4496 | 193k | if (new_len > len()) { |
4497 | 193k | size_t count = new_len - len(); |
4498 | 193k | limb *first = data + len(); |
4499 | 193k | limb *last = first + count; |
4500 | 193k | tinyobj_ff::fill(first, last, value); |
4501 | 193k | set_len(new_len); |
4502 | 193k | } else { |
4503 | 0 | set_len(new_len); |
4504 | 0 | } |
4505 | 193k | } |
4506 | | |
4507 | | // try to resize the vector, returning if the vector was resized. |
4508 | 193k | FASTFLOAT_CONSTEXPR20 bool try_resize(size_t new_len, limb value) noexcept { |
4509 | 193k | if (new_len > capacity()) { |
4510 | 0 | return false; |
4511 | 193k | } else { |
4512 | 193k | resize_unchecked(new_len, value); |
4513 | 193k | return true; |
4514 | 193k | } |
4515 | 193k | } |
4516 | | |
4517 | | // check if any limbs are non-zero after the given index. |
4518 | | // this needs to be done in reverse order, since the index |
4519 | | // is relative to the most significant limbs. |
4520 | 7.55k | FASTFLOAT_CONSTEXPR14 bool nonzero(size_t index) const noexcept { |
4521 | 10.3k | while (index < len()) { |
4522 | 6.74k | if (rindex(index) != 0) { |
4523 | 3.94k | return true; |
4524 | 3.94k | } |
4525 | 2.79k | index++; |
4526 | 2.79k | } |
4527 | 3.60k | return false; |
4528 | 7.55k | } |
4529 | | |
4530 | | // normalize the big integer, so most-significant zero limbs are removed. |
4531 | 71.4k | FASTFLOAT_CONSTEXPR14 void normalize() noexcept { |
4532 | 71.4k | while (len() > 0 && rindex(0) == 0) { |
4533 | 0 | length--; |
4534 | 0 | } |
4535 | 71.4k | } |
4536 | | }; |
4537 | | |
4538 | | fastfloat_really_inline FASTFLOAT_CONSTEXPR14 uint64_t |
4539 | 0 | empty_hi64(bool &truncated) noexcept { |
4540 | 0 | truncated = false; |
4541 | 0 | return 0; |
4542 | 0 | } |
4543 | | |
4544 | | fastfloat_really_inline FASTFLOAT_CONSTEXPR20 uint64_t |
4545 | 4.90k | uint64_hi64(uint64_t r0, bool &truncated) noexcept { |
4546 | 4.90k | truncated = false; |
4547 | 4.90k | int shl = leading_zeroes(r0); |
4548 | 4.90k | return r0 << shl; |
4549 | 4.90k | } |
4550 | | |
4551 | | fastfloat_really_inline FASTFLOAT_CONSTEXPR20 uint64_t |
4552 | 7.55k | uint64_hi64(uint64_t r0, uint64_t r1, bool &truncated) noexcept { |
4553 | 7.55k | int shl = leading_zeroes(r0); |
4554 | 7.55k | if (shl == 0) { |
4555 | 439 | truncated = r1 != 0; |
4556 | 439 | return r0; |
4557 | 7.11k | } else { |
4558 | 7.11k | int shr = 64 - shl; |
4559 | 7.11k | truncated = (r1 << shl) != 0; |
4560 | 7.11k | return (r0 << shl) | (r1 >> shr); |
4561 | 7.11k | } |
4562 | 7.55k | } |
4563 | | |
4564 | | fastfloat_really_inline FASTFLOAT_CONSTEXPR20 uint64_t |
4565 | 0 | uint32_hi64(uint32_t r0, bool &truncated) noexcept { |
4566 | 0 | return uint64_hi64(r0, truncated); |
4567 | 0 | } |
4568 | | |
4569 | | fastfloat_really_inline FASTFLOAT_CONSTEXPR20 uint64_t |
4570 | 0 | uint32_hi64(uint32_t r0, uint32_t r1, bool &truncated) noexcept { |
4571 | 0 | uint64_t x0 = r0; |
4572 | 0 | uint64_t x1 = r1; |
4573 | 0 | return uint64_hi64((x0 << 32) | x1, truncated); |
4574 | 0 | } |
4575 | | |
4576 | | fastfloat_really_inline FASTFLOAT_CONSTEXPR20 uint64_t |
4577 | 0 | uint32_hi64(uint32_t r0, uint32_t r1, uint32_t r2, bool &truncated) noexcept { |
4578 | 0 | uint64_t x0 = r0; |
4579 | 0 | uint64_t x1 = r1; |
4580 | 0 | uint64_t x2 = r2; |
4581 | 0 | return uint64_hi64(x0, (x1 << 32) | x2, truncated); |
4582 | 0 | } |
4583 | | |
4584 | | // add two small integers, checking for overflow. |
4585 | | // we want an efficient operation. for msvc, where |
4586 | | // we don't have built-in intrinsics, this is still |
4587 | | // pretty fast. |
4588 | | fastfloat_really_inline FASTFLOAT_CONSTEXPR20 limb |
4589 | 3.20M | scalar_add(limb x, limb y, bool &overflow) noexcept { |
4590 | 3.20M | limb z; |
4591 | | // gcc and clang |
4592 | 3.20M | #if defined(__has_builtin) |
4593 | 3.20M | #if __has_builtin(__builtin_add_overflow) |
4594 | 3.20M | if (!cpp20_and_in_constexpr()) { |
4595 | 3.20M | overflow = __builtin_add_overflow(x, y, &z); |
4596 | 3.20M | return z; |
4597 | 3.20M | } |
4598 | 0 | #endif |
4599 | 0 | #endif |
4600 | | |
4601 | | // generic, this still optimizes correctly on MSVC. |
4602 | 0 | z = x + y; |
4603 | 0 | overflow = z < x; |
4604 | 0 | return z; |
4605 | 3.20M | } |
4606 | | |
4607 | | // multiply two small integers, getting both the high and low bits. |
4608 | | fastfloat_really_inline FASTFLOAT_CONSTEXPR20 limb |
4609 | 9.96M | scalar_mul(limb x, limb y, limb &carry) noexcept { |
4610 | 9.96M | #ifdef FASTFLOAT_64BIT_LIMB |
4611 | 9.96M | #if defined(__SIZEOF_INT128__) |
4612 | | // GCC and clang both define it as an extension. |
4613 | 9.96M | __uint128_t z = __uint128_t(x) * __uint128_t(y) + __uint128_t(carry); |
4614 | 9.96M | carry = limb(z >> limb_bits); |
4615 | 9.96M | return limb(z); |
4616 | | #else |
4617 | | // fallback, no native 128-bit integer multiplication with carry. |
4618 | | // on msvc, this optimizes identically, somehow. |
4619 | | value128 z = full_multiplication(x, y); |
4620 | | bool overflow; |
4621 | | z.low = scalar_add(z.low, carry, overflow); |
4622 | | z.high += uint64_t(overflow); // cannot overflow |
4623 | | carry = z.high; |
4624 | | return z.low; |
4625 | | #endif |
4626 | | #else |
4627 | | uint64_t z = uint64_t(x) * uint64_t(y) + uint64_t(carry); |
4628 | | carry = limb(z >> limb_bits); |
4629 | | return limb(z); |
4630 | | #endif |
4631 | 9.96M | } |
4632 | | |
4633 | | // add scalar value to bigint starting from offset. |
4634 | | // used in grade school multiplication |
4635 | | template <uint16_t size> |
4636 | | inline FASTFLOAT_CONSTEXPR20 bool small_add_from(stackvec<size> &vec, limb y, |
4637 | 420k | size_t start) noexcept { |
4638 | 420k | size_t index = start; |
4639 | 420k | limb carry = y; |
4640 | 420k | bool overflow; |
4641 | 548k | while (carry != 0 && index < vec.len()) { |
4642 | 128k | vec[index] = scalar_add(vec[index], carry, overflow); |
4643 | 128k | carry = limb(overflow); |
4644 | 128k | index += 1; |
4645 | 128k | } |
4646 | 420k | if (carry != 0) { |
4647 | 35.1k | FASTFLOAT_TRY(vec.try_push(carry)); |
4648 | 35.1k | } |
4649 | 420k | return true; |
4650 | 420k | } |
4651 | | |
4652 | | // add scalar value to bigint. |
4653 | | template <uint16_t size> |
4654 | | fastfloat_really_inline FASTFLOAT_CONSTEXPR20 bool |
4655 | 420k | small_add(stackvec<size> &vec, limb y) noexcept { |
4656 | 420k | return small_add_from(vec, y, 0); |
4657 | 420k | } |
4658 | | |
4659 | | // multiply bigint by scalar value. |
4660 | | template <uint16_t size> |
4661 | | inline FASTFLOAT_CONSTEXPR20 bool small_mul(stackvec<size> &vec, |
4662 | 733k | limb y) noexcept { |
4663 | 733k | limb carry = 0; |
4664 | 10.7M | for (size_t index = 0; index < vec.len(); index++) { |
4665 | 9.96M | vec[index] = scalar_mul(vec[index], y, carry); |
4666 | 9.96M | } |
4667 | 733k | if (carry != 0) { |
4668 | 659k | FASTFLOAT_TRY(vec.try_push(carry)); |
4669 | 659k | } |
4670 | 733k | return true; |
4671 | 733k | } |
4672 | | |
4673 | | // add bigint to bigint starting from index. |
4674 | | // used in grade school multiplication |
4675 | | template <uint16_t size> |
4676 | | FASTFLOAT_CONSTEXPR20 bool large_add_from(stackvec<size> &x, limb_span y, |
4677 | 195k | size_t start) noexcept { |
4678 | | // the effective x buffer is from `xstart..x.len()`, so exit early |
4679 | | // if we can't get that current range. |
4680 | 195k | if (x.len() < start || y.len() > x.len() - start) { |
4681 | 193k | FASTFLOAT_TRY(x.try_resize(y.len() + start, 0)); |
4682 | 193k | } |
4683 | | |
4684 | 195k | bool carry = false; |
4685 | 2.37M | for (size_t index = 0; index < y.len(); index++) { |
4686 | 2.17M | limb xi = x[index + start]; |
4687 | 2.17M | limb yi = y[index]; |
4688 | 2.17M | bool c1 = false; |
4689 | 2.17M | bool c2 = false; |
4690 | 2.17M | xi = scalar_add(xi, yi, c1); |
4691 | 2.17M | if (carry) { |
4692 | 900k | xi = scalar_add(xi, 1, c2); |
4693 | 900k | } |
4694 | 2.17M | x[index + start] = xi; |
4695 | 2.17M | carry = c1 | c2; |
4696 | 2.17M | } |
4697 | | |
4698 | | // handle overflow |
4699 | 195k | if (carry) { |
4700 | 0 | FASTFLOAT_TRY(small_add_from(x, 1, y.len() + start)); |
4701 | 0 | } |
4702 | 195k | return true; |
4703 | 195k | } |
4704 | | |
4705 | | // add bigint to bigint. |
4706 | | template <uint16_t size> |
4707 | | fastfloat_really_inline FASTFLOAT_CONSTEXPR20 bool |
4708 | | large_add_from(stackvec<size> &x, limb_span y) noexcept { |
4709 | | return large_add_from(x, y, 0); |
4710 | | } |
4711 | | |
4712 | | // grade-school multiplication algorithm |
4713 | | template <uint16_t size> |
4714 | 48.7k | FASTFLOAT_CONSTEXPR20 bool long_mul(stackvec<size> &x, limb_span y) noexcept { |
4715 | 48.7k | limb_span xs = limb_span(x.data, x.len()); |
4716 | 48.7k | stackvec<size> z(xs); |
4717 | 48.7k | limb_span zs = limb_span(z.data, z.len()); |
4718 | | |
4719 | 48.7k | if (y.len() != 0) { |
4720 | 48.7k | limb y0 = y[0]; |
4721 | 48.7k | FASTFLOAT_TRY(small_mul(x, y0)); |
4722 | 243k | for (size_t index = 1; index < y.len(); index++) { |
4723 | 195k | limb yi = y[index]; |
4724 | 195k | stackvec<size> zi; |
4725 | 195k | if (yi != 0) { |
4726 | | // re-use the same buffer throughout |
4727 | 195k | zi.set_len(0); |
4728 | 195k | FASTFLOAT_TRY(zi.try_extend(zs)); |
4729 | 195k | FASTFLOAT_TRY(small_mul(zi, yi)); |
4730 | 195k | limb_span zis = limb_span(zi.data, zi.len()); |
4731 | 195k | FASTFLOAT_TRY(large_add_from(x, zis, index)); |
4732 | 195k | } |
4733 | 195k | } |
4734 | 48.7k | } |
4735 | | |
4736 | 48.7k | x.normalize(); |
4737 | 48.7k | return true; |
4738 | 48.7k | } |
4739 | | |
4740 | | // grade-school multiplication algorithm |
4741 | | template <uint16_t size> |
4742 | 48.7k | FASTFLOAT_CONSTEXPR20 bool large_mul(stackvec<size> &x, limb_span y) noexcept { |
4743 | 48.7k | if (y.len() == 1) { |
4744 | 0 | FASTFLOAT_TRY(small_mul(x, y[0])); |
4745 | 48.7k | } else { |
4746 | 48.7k | FASTFLOAT_TRY(long_mul(x, y)); |
4747 | 48.7k | } |
4748 | 48.7k | return true; |
4749 | 48.7k | } |
4750 | | |
4751 | | template <typename = void> struct pow5_tables { |
4752 | | static constexpr uint32_t large_step = 135; |
4753 | | static constexpr uint64_t small_power_of_5[] = { |
4754 | | 1UL, |
4755 | | 5UL, |
4756 | | 25UL, |
4757 | | 125UL, |
4758 | | 625UL, |
4759 | | 3125UL, |
4760 | | 15625UL, |
4761 | | 78125UL, |
4762 | | 390625UL, |
4763 | | 1953125UL, |
4764 | | 9765625UL, |
4765 | | 48828125UL, |
4766 | | 244140625UL, |
4767 | | 1220703125UL, |
4768 | | 6103515625UL, |
4769 | | 30517578125UL, |
4770 | | 152587890625UL, |
4771 | | 762939453125UL, |
4772 | | 3814697265625UL, |
4773 | | 19073486328125UL, |
4774 | | 95367431640625UL, |
4775 | | 476837158203125UL, |
4776 | | 2384185791015625UL, |
4777 | | 11920928955078125UL, |
4778 | | 59604644775390625UL, |
4779 | | 298023223876953125UL, |
4780 | | 1490116119384765625UL, |
4781 | | 7450580596923828125UL, |
4782 | | }; |
4783 | | #ifdef FASTFLOAT_64BIT_LIMB |
4784 | | constexpr static limb large_power_of_5[] = { |
4785 | | 1414648277510068013UL, 9180637584431281687UL, 4539964771860779200UL, |
4786 | | 10482974169319127550UL, 198276706040285095UL}; |
4787 | | #else |
4788 | | constexpr static limb large_power_of_5[] = { |
4789 | | 4279965485U, 329373468U, 4020270615U, 2137533757U, 4287402176U, |
4790 | | 1057042919U, 1071430142U, 2440757623U, 381945767U, 46164893U}; |
4791 | | #endif |
4792 | | }; |
4793 | | |
4794 | | #if FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE |
4795 | | |
4796 | | template <typename T> constexpr uint32_t pow5_tables<T>::large_step; |
4797 | | |
4798 | | template <typename T> constexpr uint64_t pow5_tables<T>::small_power_of_5[]; |
4799 | | |
4800 | | template <typename T> constexpr limb pow5_tables<T>::large_power_of_5[]; |
4801 | | |
4802 | | #endif |
4803 | | |
4804 | | // big integer type. implements a small subset of big integer |
4805 | | // arithmetic, using simple algorithms since asymptotically |
4806 | | // faster algorithms are slower for a small number of limbs. |
4807 | | // all operations assume the big-integer is normalized. |
4808 | | struct bigint : pow5_tables<> { |
4809 | | // storage of the limbs, in little-endian order. |
4810 | | stackvec<bigint_limbs> vec; |
4811 | | |
4812 | 35.1k | FASTFLOAT_CONSTEXPR20 bigint() : vec() {} |
4813 | | |
4814 | | bigint(bigint const &) = delete; |
4815 | | bigint &operator=(bigint const &) = delete; |
4816 | | bigint(bigint &&) = delete; |
4817 | | bigint &operator=(bigint &&other) = delete; |
4818 | | |
4819 | 22.7k | FASTFLOAT_CONSTEXPR20 bigint(uint64_t value) : vec() { |
4820 | 22.7k | #ifdef FASTFLOAT_64BIT_LIMB |
4821 | 22.7k | vec.push_unchecked(value); |
4822 | | #else |
4823 | | vec.push_unchecked(uint32_t(value)); |
4824 | | vec.push_unchecked(uint32_t(value >> 32)); |
4825 | | #endif |
4826 | 22.7k | vec.normalize(); |
4827 | 22.7k | } |
4828 | | |
4829 | | // get the high 64 bits from the vector, and if bits were truncated. |
4830 | | // this is to get the significant digits for the float. |
4831 | 12.4k | FASTFLOAT_CONSTEXPR20 uint64_t hi64(bool &truncated) const noexcept { |
4832 | 12.4k | #ifdef FASTFLOAT_64BIT_LIMB |
4833 | 12.4k | if (vec.len() == 0) { |
4834 | 0 | return empty_hi64(truncated); |
4835 | 12.4k | } else if (vec.len() == 1) { |
4836 | 4.90k | return uint64_hi64(vec.rindex(0), truncated); |
4837 | 7.55k | } else { |
4838 | 7.55k | uint64_t result = uint64_hi64(vec.rindex(0), vec.rindex(1), truncated); |
4839 | 7.55k | truncated |= vec.nonzero(2); |
4840 | 7.55k | return result; |
4841 | 7.55k | } |
4842 | | #else |
4843 | | if (vec.len() == 0) { |
4844 | | return empty_hi64(truncated); |
4845 | | } else if (vec.len() == 1) { |
4846 | | return uint32_hi64(vec.rindex(0), truncated); |
4847 | | } else if (vec.len() == 2) { |
4848 | | return uint32_hi64(vec.rindex(0), vec.rindex(1), truncated); |
4849 | | } else { |
4850 | | uint64_t result = |
4851 | | uint32_hi64(vec.rindex(0), vec.rindex(1), vec.rindex(2), truncated); |
4852 | | truncated |= vec.nonzero(3); |
4853 | | return result; |
4854 | | } |
4855 | | #endif |
4856 | 12.4k | } |
4857 | | |
4858 | | // compare two big integers, returning the large value. |
4859 | | // assumes both are normalized. if the return value is |
4860 | | // negative, other is larger, if the return value is |
4861 | | // positive, this is larger, otherwise they are equal. |
4862 | | // the limbs are stored in little-endian order, so we |
4863 | | // must compare the limbs in ever order. |
4864 | 22.7k | FASTFLOAT_CONSTEXPR20 int compare(bigint const &other) const noexcept { |
4865 | 22.7k | if (vec.len() > other.vec.len()) { |
4866 | 0 | return 1; |
4867 | 22.7k | } else if (vec.len() < other.vec.len()) { |
4868 | 0 | return -1; |
4869 | 22.7k | } else { |
4870 | 78.3k | for (size_t index = vec.len(); index > 0; index--) { |
4871 | 77.2k | limb xi = vec[index - 1]; |
4872 | 77.2k | limb yi = other.vec[index - 1]; |
4873 | 77.2k | if (xi > yi) { |
4874 | 6.63k | return 1; |
4875 | 70.6k | } else if (xi < yi) { |
4876 | 14.9k | return -1; |
4877 | 14.9k | } |
4878 | 77.2k | } |
4879 | 1.08k | return 0; |
4880 | 22.7k | } |
4881 | 22.7k | } |
4882 | | |
4883 | | // shift left each limb n bits, carrying over to the new limb |
4884 | | // returns true if we were able to shift all the digits. |
4885 | 22.2k | FASTFLOAT_CONSTEXPR20 bool shl_bits(size_t n) noexcept { |
4886 | | // Internally, for each item, we shift left by n, and add the previous |
4887 | | // right shifted limb-bits. |
4888 | | // For example, we transform (for u8) shifted left 2, to: |
4889 | | // b10100100 b01000010 |
4890 | | // b10 b10010001 b00001000 |
4891 | 22.2k | FASTFLOAT_DEBUG_ASSERT(n != 0); |
4892 | 22.2k | FASTFLOAT_DEBUG_ASSERT(n < sizeof(limb) * 8); |
4893 | | |
4894 | 22.2k | size_t shl = n; |
4895 | 22.2k | size_t shr = limb_bits - shl; |
4896 | 22.2k | limb prev = 0; |
4897 | 312k | for (size_t index = 0; index < vec.len(); index++) { |
4898 | 290k | limb xi = vec[index]; |
4899 | 290k | vec[index] = (xi << shl) | (prev >> shr); |
4900 | 290k | prev = xi; |
4901 | 290k | } |
4902 | | |
4903 | 22.2k | limb carry = prev >> shr; |
4904 | 22.2k | if (carry != 0) { |
4905 | 6.77k | return vec.try_push(carry); |
4906 | 6.77k | } |
4907 | 15.4k | return true; |
4908 | 22.2k | } |
4909 | | |
4910 | | // move the limbs left by `n` limbs. |
4911 | 20.4k | FASTFLOAT_CONSTEXPR20 bool shl_limbs(size_t n) noexcept { |
4912 | 20.4k | FASTFLOAT_DEBUG_ASSERT(n != 0); |
4913 | 20.4k | if (n + vec.len() > vec.capacity()) { |
4914 | 0 | return false; |
4915 | 20.4k | } else if (!vec.is_empty()) { |
4916 | | // move limbs |
4917 | 20.4k | limb *dst = vec.data + n; |
4918 | 20.4k | limb const *src = vec.data; |
4919 | 20.4k | tinyobj_ff::copy_backward(src, src + vec.len(), dst + vec.len()); |
4920 | | // fill in empty limbs |
4921 | 20.4k | limb *first = vec.data; |
4922 | 20.4k | limb *last = first + n; |
4923 | 20.4k | tinyobj_ff::fill(first, last, 0); |
4924 | 20.4k | vec.set_len(n + vec.len()); |
4925 | 20.4k | return true; |
4926 | 20.4k | } else { |
4927 | 0 | return true; |
4928 | 0 | } |
4929 | 20.4k | } |
4930 | | |
4931 | | // move the limbs left by `n` bits. |
4932 | 34.8k | FASTFLOAT_CONSTEXPR20 bool shl(size_t n) noexcept { |
4933 | 34.8k | size_t rem = n % limb_bits; |
4934 | 34.8k | size_t div = n / limb_bits; |
4935 | 34.8k | if (rem != 0) { |
4936 | 22.2k | FASTFLOAT_TRY(shl_bits(rem)); |
4937 | 22.2k | } |
4938 | 34.8k | if (div != 0) { |
4939 | 20.4k | FASTFLOAT_TRY(shl_limbs(div)); |
4940 | 20.4k | } |
4941 | 34.8k | return true; |
4942 | 34.8k | } |
4943 | | |
4944 | | // get the number of leading zeros in the bigint. |
4945 | 12.4k | FASTFLOAT_CONSTEXPR20 int ctlz() const noexcept { |
4946 | 12.4k | if (vec.is_empty()) { |
4947 | 0 | return 0; |
4948 | 12.4k | } else { |
4949 | 12.4k | #ifdef FASTFLOAT_64BIT_LIMB |
4950 | 12.4k | return leading_zeroes(vec.rindex(0)); |
4951 | | #else |
4952 | | // no use defining a specialized leading_zeroes for a 32-bit type. |
4953 | | uint64_t r0 = vec.rindex(0); |
4954 | | return leading_zeroes(r0 << 32); |
4955 | | #endif |
4956 | 12.4k | } |
4957 | 12.4k | } |
4958 | | |
4959 | | // get the number of bits in the bigint. |
4960 | 12.4k | FASTFLOAT_CONSTEXPR20 int bit_length() const noexcept { |
4961 | 12.4k | int lz = ctlz(); |
4962 | 12.4k | return int(limb_bits * vec.len()) - lz; |
4963 | 12.4k | } |
4964 | | |
4965 | 420k | FASTFLOAT_CONSTEXPR20 bool mul(limb y) noexcept { return small_mul(vec, y); } |
4966 | | |
4967 | 420k | FASTFLOAT_CONSTEXPR20 bool add(limb y) noexcept { return small_add(vec, y); } |
4968 | | |
4969 | | // multiply as if by 2 raised to a power. |
4970 | 34.8k | FASTFLOAT_CONSTEXPR20 bool pow2(uint32_t exp) noexcept { return shl(exp); } |
4971 | | |
4972 | | // multiply as if by 5 raised to a power. |
4973 | 35.1k | FASTFLOAT_CONSTEXPR20 bool pow5(uint32_t exp) noexcept { |
4974 | | // multiply by a power of 5 |
4975 | 35.1k | size_t large_length = sizeof(large_power_of_5) / sizeof(limb); |
4976 | 35.1k | limb_span large = limb_span(large_power_of_5, large_length); |
4977 | 83.9k | while (exp >= large_step) { |
4978 | 48.7k | FASTFLOAT_TRY(large_mul(vec, large)); |
4979 | 48.7k | exp -= large_step; |
4980 | 48.7k | } |
4981 | 35.1k | #ifdef FASTFLOAT_64BIT_LIMB |
4982 | 35.1k | uint32_t small_step = 27; |
4983 | 35.1k | limb max_native = 7450580596923828125UL; |
4984 | | #else |
4985 | | uint32_t small_step = 13; |
4986 | | limb max_native = 1220703125U; |
4987 | | #endif |
4988 | 79.8k | while (exp >= small_step) { |
4989 | 44.6k | FASTFLOAT_TRY(small_mul(vec, max_native)); |
4990 | 44.6k | exp -= small_step; |
4991 | 44.6k | } |
4992 | 35.1k | if (exp != 0) { |
4993 | | // Work around clang bug https://godbolt.org/z/zedh7rrhc |
4994 | | // This is similar to https://github.com/llvm/llvm-project/issues/47746, |
4995 | | // except the workaround described there don't work here |
4996 | 24.3k | FASTFLOAT_TRY(small_mul( |
4997 | 24.3k | vec, limb(((void)small_power_of_5[0], small_power_of_5[exp])))); |
4998 | 24.3k | } |
4999 | | |
5000 | 35.1k | return true; |
5001 | 35.1k | } |
5002 | | |
5003 | | // multiply as if by 10 raised to a power. |
5004 | 12.4k | FASTFLOAT_CONSTEXPR20 bool pow10(uint32_t exp) noexcept { |
5005 | 12.4k | FASTFLOAT_TRY(pow5(exp)); |
5006 | 12.4k | return pow2(exp); |
5007 | 12.4k | } |
5008 | | }; |
5009 | | |
5010 | | } // namespace fast_float |
5011 | | |
5012 | | #endif |
5013 | | |
5014 | | #ifndef FASTFLOAT_DIGIT_COMPARISON_H |
5015 | | #define FASTFLOAT_DIGIT_COMPARISON_H |
5016 | | |
5017 | | #include <cstring> |
5018 | | |
5019 | | |
5020 | | namespace fast_float { |
5021 | | |
5022 | | // 1e0 to 1e19 |
5023 | | constexpr static uint64_t powers_of_ten_uint64[] = {1UL, |
5024 | | 10UL, |
5025 | | 100UL, |
5026 | | 1000UL, |
5027 | | 10000UL, |
5028 | | 100000UL, |
5029 | | 1000000UL, |
5030 | | 10000000UL, |
5031 | | 100000000UL, |
5032 | | 1000000000UL, |
5033 | | 10000000000UL, |
5034 | | 100000000000UL, |
5035 | | 1000000000000UL, |
5036 | | 10000000000000UL, |
5037 | | 100000000000000UL, |
5038 | | 1000000000000000UL, |
5039 | | 10000000000000000UL, |
5040 | | 100000000000000000UL, |
5041 | | 1000000000000000000UL, |
5042 | | 10000000000000000000UL}; |
5043 | | |
5044 | | // calculate the exponent, in scientific notation, of the number. |
5045 | | // this algorithm is not even close to optimized, but it has no practical |
5046 | | // effect on performance: in order to have a faster algorithm, we'd need |
5047 | | // to slow down performance for faster algorithms, and this is still fast. |
5048 | | template <typename UC> |
5049 | | fastfloat_really_inline FASTFLOAT_CONSTEXPR14 int32_t |
5050 | 35.1k | scientific_exponent(parsed_number_string_t<UC> &num) noexcept { |
5051 | 35.1k | uint64_t mantissa = num.mantissa; |
5052 | 35.1k | int32_t exponent = int32_t(num.exponent); |
5053 | 175k | while (mantissa >= 10000) { |
5054 | 140k | mantissa /= 10000; |
5055 | 140k | exponent += 4; |
5056 | 140k | } |
5057 | 70.3k | while (mantissa >= 100) { |
5058 | 35.1k | mantissa /= 100; |
5059 | 35.1k | exponent += 2; |
5060 | 35.1k | } |
5061 | 35.1k | while (mantissa >= 10) { |
5062 | 0 | mantissa /= 10; |
5063 | 0 | exponent += 1; |
5064 | 0 | } |
5065 | 35.1k | return exponent; |
5066 | 35.1k | } |
5067 | | |
5068 | | // this converts a native floating-point number to an extended-precision float. |
5069 | | template <typename T> |
5070 | | fastfloat_really_inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa |
5071 | 22.7k | to_extended(T value) noexcept { |
5072 | 22.7k | using equiv_uint = equiv_uint_t<T>; |
5073 | 22.7k | constexpr equiv_uint exponent_mask = binary_format<T>::exponent_mask(); |
5074 | 22.7k | constexpr equiv_uint mantissa_mask = binary_format<T>::mantissa_mask(); |
5075 | 22.7k | constexpr equiv_uint hidden_bit_mask = binary_format<T>::hidden_bit_mask(); |
5076 | | |
5077 | 22.7k | adjusted_mantissa am; |
5078 | 22.7k | int32_t bias = binary_format<T>::mantissa_explicit_bits() - |
5079 | 22.7k | binary_format<T>::minimum_exponent(); |
5080 | 22.7k | equiv_uint bits; |
5081 | | #if FASTFLOAT_HAS_BIT_CAST |
5082 | | bits = std::bit_cast<equiv_uint>(value); |
5083 | | #else |
5084 | 22.7k | ::memcpy(&bits, &value, sizeof(T)); |
5085 | 22.7k | #endif |
5086 | 22.7k | if ((bits & exponent_mask) == 0) { |
5087 | | // denormal |
5088 | 2.09k | am.power2 = 1 - bias; |
5089 | 2.09k | am.mantissa = bits & mantissa_mask; |
5090 | 20.6k | } else { |
5091 | | // normal |
5092 | 20.6k | am.power2 = int32_t((bits & exponent_mask) >> |
5093 | 20.6k | binary_format<T>::mantissa_explicit_bits()); |
5094 | 20.6k | am.power2 -= bias; |
5095 | 20.6k | am.mantissa = (bits & mantissa_mask) | hidden_bit_mask; |
5096 | 20.6k | } |
5097 | | |
5098 | 22.7k | return am; |
5099 | 22.7k | } fast_float::adjusted_mantissa fast_float::to_extended<double>(double) Line | Count | Source | 5071 | 22.7k | to_extended(T value) noexcept { | 5072 | 22.7k | using equiv_uint = equiv_uint_t<T>; | 5073 | 22.7k | constexpr equiv_uint exponent_mask = binary_format<T>::exponent_mask(); | 5074 | 22.7k | constexpr equiv_uint mantissa_mask = binary_format<T>::mantissa_mask(); | 5075 | 22.7k | constexpr equiv_uint hidden_bit_mask = binary_format<T>::hidden_bit_mask(); | 5076 | | | 5077 | 22.7k | adjusted_mantissa am; | 5078 | 22.7k | int32_t bias = binary_format<T>::mantissa_explicit_bits() - | 5079 | 22.7k | binary_format<T>::minimum_exponent(); | 5080 | 22.7k | equiv_uint bits; | 5081 | | #if FASTFLOAT_HAS_BIT_CAST | 5082 | | bits = std::bit_cast<equiv_uint>(value); | 5083 | | #else | 5084 | 22.7k | ::memcpy(&bits, &value, sizeof(T)); | 5085 | 22.7k | #endif | 5086 | 22.7k | if ((bits & exponent_mask) == 0) { | 5087 | | // denormal | 5088 | 2.09k | am.power2 = 1 - bias; | 5089 | 2.09k | am.mantissa = bits & mantissa_mask; | 5090 | 20.6k | } else { | 5091 | | // normal | 5092 | 20.6k | am.power2 = int32_t((bits & exponent_mask) >> | 5093 | 20.6k | binary_format<T>::mantissa_explicit_bits()); | 5094 | 20.6k | am.power2 -= bias; | 5095 | 20.6k | am.mantissa = (bits & mantissa_mask) | hidden_bit_mask; | 5096 | 20.6k | } | 5097 | | | 5098 | 22.7k | return am; | 5099 | 22.7k | } |
Unexecuted instantiation: fast_float::adjusted_mantissa fast_float::to_extended<float>(float) |
5100 | | |
5101 | | // get the extended precision value of the halfway point between b and b+u. |
5102 | | // we are given a native float that represents b, so we need to adjust it |
5103 | | // halfway between b and b+u. |
5104 | | template <typename T> |
5105 | | fastfloat_really_inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa |
5106 | 22.7k | to_extended_halfway(T value) noexcept { |
5107 | 22.7k | adjusted_mantissa am = to_extended(value); |
5108 | 22.7k | am.mantissa <<= 1; |
5109 | 22.7k | am.mantissa += 1; |
5110 | 22.7k | am.power2 -= 1; |
5111 | 22.7k | return am; |
5112 | 22.7k | } fast_float::adjusted_mantissa fast_float::to_extended_halfway<double>(double) Line | Count | Source | 5106 | 22.7k | to_extended_halfway(T value) noexcept { | 5107 | 22.7k | adjusted_mantissa am = to_extended(value); | 5108 | 22.7k | am.mantissa <<= 1; | 5109 | 22.7k | am.mantissa += 1; | 5110 | 22.7k | am.power2 -= 1; | 5111 | 22.7k | return am; | 5112 | 22.7k | } |
Unexecuted instantiation: fast_float::adjusted_mantissa fast_float::to_extended_halfway<float>(float) |
5113 | | |
5114 | | // round an extended-precision float to the nearest machine float. |
5115 | | template <typename T, typename callback> |
5116 | | fastfloat_really_inline FASTFLOAT_CONSTEXPR14 void round(adjusted_mantissa &am, |
5117 | 57.8k | callback cb) noexcept { |
5118 | 57.8k | int32_t mantissa_shift = 64 - binary_format<T>::mantissa_explicit_bits() - 1; |
5119 | 57.8k | if (-am.power2 >= mantissa_shift) { |
5120 | | // have a denormal float |
5121 | 4.19k | int32_t shift = -am.power2 + 1; |
5122 | 4.19k | cb(am, tinyobj_ff::min_val<int32_t>(shift, 64)); |
5123 | | // check for round-up: if rounding-nearest carried us to the hidden bit. |
5124 | 4.19k | am.power2 = (am.mantissa < |
5125 | 4.19k | (uint64_t(1) << binary_format<T>::mantissa_explicit_bits())) |
5126 | 4.19k | ? 0 |
5127 | 4.19k | : 1; |
5128 | 4.19k | return; |
5129 | 4.19k | } |
5130 | | |
5131 | | // have a normal float, use the default shift. |
5132 | 53.6k | cb(am, mantissa_shift); |
5133 | | |
5134 | | // check for carry |
5135 | 53.6k | if (am.mantissa >= |
5136 | 53.6k | (uint64_t(2) << binary_format<T>::mantissa_explicit_bits())) { |
5137 | 542 | am.mantissa = (uint64_t(1) << binary_format<T>::mantissa_explicit_bits()); |
5138 | 542 | am.power2++; |
5139 | 542 | } |
5140 | | |
5141 | | // check for infinite: we could have carried to an infinite power |
5142 | 53.6k | am.mantissa &= ~(uint64_t(1) << binary_format<T>::mantissa_explicit_bits()); |
5143 | 53.6k | if (am.power2 >= binary_format<T>::infinite_power()) { |
5144 | 0 | am.power2 = binary_format<T>::infinite_power(); |
5145 | 0 | am.mantissa = 0; |
5146 | 0 | } |
5147 | 53.6k | } void fast_float::round<double, fast_float::positive_digit_comp<double>(fast_float::bigint&, int)::{lambda(fast_float::adjusted_mantissa&, int)#1}>(fast_float::adjusted_mantissa&, fast_float::positive_digit_comp<double>(fast_float::bigint&, int)::{lambda(fast_float::adjusted_mantissa&, int)#1})Line | Count | Source | 5117 | 12.4k | callback cb) noexcept { | 5118 | 12.4k | int32_t mantissa_shift = 64 - binary_format<T>::mantissa_explicit_bits() - 1; | 5119 | 12.4k | if (-am.power2 >= mantissa_shift) { | 5120 | | // have a denormal float | 5121 | 0 | int32_t shift = -am.power2 + 1; | 5122 | 0 | cb(am, tinyobj_ff::min_val<int32_t>(shift, 64)); | 5123 | | // check for round-up: if rounding-nearest carried us to the hidden bit. | 5124 | 0 | am.power2 = (am.mantissa < | 5125 | 0 | (uint64_t(1) << binary_format<T>::mantissa_explicit_bits())) | 5126 | 0 | ? 0 | 5127 | 0 | : 1; | 5128 | 0 | return; | 5129 | 0 | } | 5130 | | | 5131 | | // have a normal float, use the default shift. | 5132 | 12.4k | cb(am, mantissa_shift); | 5133 | | | 5134 | | // check for carry | 5135 | 12.4k | if (am.mantissa >= | 5136 | 12.4k | (uint64_t(2) << binary_format<T>::mantissa_explicit_bits())) { | 5137 | 430 | am.mantissa = (uint64_t(1) << binary_format<T>::mantissa_explicit_bits()); | 5138 | 430 | am.power2++; | 5139 | 430 | } | 5140 | | | 5141 | | // check for infinite: we could have carried to an infinite power | 5142 | 12.4k | am.mantissa &= ~(uint64_t(1) << binary_format<T>::mantissa_explicit_bits()); | 5143 | 12.4k | if (am.power2 >= binary_format<T>::infinite_power()) { | 5144 | 0 | am.power2 = binary_format<T>::infinite_power(); | 5145 | 0 | am.mantissa = 0; | 5146 | 0 | } | 5147 | 12.4k | } |
void fast_float::round<double, fast_float::negative_digit_comp<double>(fast_float::bigint&, fast_float::adjusted_mantissa, int)::{lambda(fast_float::adjusted_mantissa&, int)#1}>(fast_float::adjusted_mantissa&, fast_float::negative_digit_comp<double>(fast_float::bigint&, fast_float::adjusted_mantissa, int)::{lambda(fast_float::adjusted_mantissa&, int)#1})Line | Count | Source | 5117 | 22.7k | callback cb) noexcept { | 5118 | 22.7k | int32_t mantissa_shift = 64 - binary_format<T>::mantissa_explicit_bits() - 1; | 5119 | 22.7k | if (-am.power2 >= mantissa_shift) { | 5120 | | // have a denormal float | 5121 | 2.09k | int32_t shift = -am.power2 + 1; | 5122 | 2.09k | cb(am, tinyobj_ff::min_val<int32_t>(shift, 64)); | 5123 | | // check for round-up: if rounding-nearest carried us to the hidden bit. | 5124 | 2.09k | am.power2 = (am.mantissa < | 5125 | 2.09k | (uint64_t(1) << binary_format<T>::mantissa_explicit_bits())) | 5126 | 2.09k | ? 0 | 5127 | 2.09k | : 1; | 5128 | 2.09k | return; | 5129 | 2.09k | } | 5130 | | | 5131 | | // have a normal float, use the default shift. | 5132 | 20.6k | cb(am, mantissa_shift); | 5133 | | | 5134 | | // check for carry | 5135 | 20.6k | if (am.mantissa >= | 5136 | 20.6k | (uint64_t(2) << binary_format<T>::mantissa_explicit_bits())) { | 5137 | 0 | am.mantissa = (uint64_t(1) << binary_format<T>::mantissa_explicit_bits()); | 5138 | 0 | am.power2++; | 5139 | 0 | } | 5140 | | | 5141 | | // check for infinite: we could have carried to an infinite power | 5142 | 20.6k | am.mantissa &= ~(uint64_t(1) << binary_format<T>::mantissa_explicit_bits()); | 5143 | 20.6k | if (am.power2 >= binary_format<T>::infinite_power()) { | 5144 | 0 | am.power2 = binary_format<T>::infinite_power(); | 5145 | 0 | am.mantissa = 0; | 5146 | 0 | } | 5147 | 20.6k | } |
void fast_float::round<double, fast_float::negative_digit_comp<double>(fast_float::bigint&, fast_float::adjusted_mantissa, int)::{lambda(fast_float::adjusted_mantissa&, int)#2}>(fast_float::adjusted_mantissa&, fast_float::negative_digit_comp<double>(fast_float::bigint&, fast_float::adjusted_mantissa, int)::{lambda(fast_float::adjusted_mantissa&, int)#2})Line | Count | Source | 5117 | 22.7k | callback cb) noexcept { | 5118 | 22.7k | int32_t mantissa_shift = 64 - binary_format<T>::mantissa_explicit_bits() - 1; | 5119 | 22.7k | if (-am.power2 >= mantissa_shift) { | 5120 | | // have a denormal float | 5121 | 2.09k | int32_t shift = -am.power2 + 1; | 5122 | 2.09k | cb(am, tinyobj_ff::min_val<int32_t>(shift, 64)); | 5123 | | // check for round-up: if rounding-nearest carried us to the hidden bit. | 5124 | 2.09k | am.power2 = (am.mantissa < | 5125 | 2.09k | (uint64_t(1) << binary_format<T>::mantissa_explicit_bits())) | 5126 | 2.09k | ? 0 | 5127 | 2.09k | : 1; | 5128 | 2.09k | return; | 5129 | 2.09k | } | 5130 | | | 5131 | | // have a normal float, use the default shift. | 5132 | 20.6k | cb(am, mantissa_shift); | 5133 | | | 5134 | | // check for carry | 5135 | 20.6k | if (am.mantissa >= | 5136 | 20.6k | (uint64_t(2) << binary_format<T>::mantissa_explicit_bits())) { | 5137 | 112 | am.mantissa = (uint64_t(1) << binary_format<T>::mantissa_explicit_bits()); | 5138 | 112 | am.power2++; | 5139 | 112 | } | 5140 | | | 5141 | | // check for infinite: we could have carried to an infinite power | 5142 | 20.6k | am.mantissa &= ~(uint64_t(1) << binary_format<T>::mantissa_explicit_bits()); | 5143 | 20.6k | if (am.power2 >= binary_format<T>::infinite_power()) { | 5144 | 0 | am.power2 = binary_format<T>::infinite_power(); | 5145 | 0 | am.mantissa = 0; | 5146 | 0 | } | 5147 | 20.6k | } |
Unexecuted instantiation: void fast_float::round<float, fast_float::positive_digit_comp<float>(fast_float::bigint&, int)::{lambda(fast_float::adjusted_mantissa&, int)#1}>(fast_float::adjusted_mantissa&, fast_float::positive_digit_comp<float>(fast_float::bigint&, int)::{lambda(fast_float::adjusted_mantissa&, int)#1})Unexecuted instantiation: void fast_float::round<float, fast_float::negative_digit_comp<float>(fast_float::bigint&, fast_float::adjusted_mantissa, int)::{lambda(fast_float::adjusted_mantissa&, int)#1}>(fast_float::adjusted_mantissa&, fast_float::negative_digit_comp<float>(fast_float::bigint&, fast_float::adjusted_mantissa, int)::{lambda(fast_float::adjusted_mantissa&, int)#1})Unexecuted instantiation: void fast_float::round<float, fast_float::negative_digit_comp<float>(fast_float::bigint&, fast_float::adjusted_mantissa, int)::{lambda(fast_float::adjusted_mantissa&, int)#2}>(fast_float::adjusted_mantissa&, fast_float::negative_digit_comp<float>(fast_float::bigint&, fast_float::adjusted_mantissa, int)::{lambda(fast_float::adjusted_mantissa&, int)#2}) |
5148 | | |
5149 | | template <typename callback> |
5150 | | fastfloat_really_inline FASTFLOAT_CONSTEXPR14 void |
5151 | | round_nearest_tie_even(adjusted_mantissa &am, int32_t shift, |
5152 | 35.1k | callback cb) noexcept { |
5153 | 35.1k | uint64_t const mask = (shift == 64) ? UINT64_MAX : (uint64_t(1) << shift) - 1; |
5154 | 35.1k | uint64_t const halfway = (shift == 0) ? 0 : uint64_t(1) << (shift - 1); |
5155 | 35.1k | uint64_t truncated_bits = am.mantissa & mask; |
5156 | 35.1k | bool is_above = truncated_bits > halfway; |
5157 | 35.1k | bool is_halfway = truncated_bits == halfway; |
5158 | | |
5159 | | // shift digits into position |
5160 | 35.1k | if (shift == 64) { |
5161 | 0 | am.mantissa = 0; |
5162 | 35.1k | } else { |
5163 | 35.1k | am.mantissa >>= shift; |
5164 | 35.1k | } |
5165 | 35.1k | am.power2 += shift; |
5166 | | |
5167 | 35.1k | bool is_odd = (am.mantissa & 1) == 1; |
5168 | 35.1k | am.mantissa += uint64_t(cb(is_odd, is_halfway, is_above)); |
5169 | 35.1k | } void fast_float::round_nearest_tie_even<fast_float::positive_digit_comp<double>(fast_float::bigint&, int)::{lambda(fast_float::adjusted_mantissa&, int)#1}::operator()(fast_float::adjusted_mantissa&, int) const::{lambda(bool, bool, bool)#1}>(fast_float::adjusted_mantissa&, int, fast_float::positive_digit_comp<double>(fast_float::bigint&, int)::{lambda(fast_float::adjusted_mantissa&, int)#1}::operator()(fast_float::adjusted_mantissa&, int) const::{lambda(bool, bool, bool)#1})Line | Count | Source | 5152 | 12.4k | callback cb) noexcept { | 5153 | 12.4k | uint64_t const mask = (shift == 64) ? UINT64_MAX : (uint64_t(1) << shift) - 1; | 5154 | 12.4k | uint64_t const halfway = (shift == 0) ? 0 : uint64_t(1) << (shift - 1); | 5155 | 12.4k | uint64_t truncated_bits = am.mantissa & mask; | 5156 | 12.4k | bool is_above = truncated_bits > halfway; | 5157 | 12.4k | bool is_halfway = truncated_bits == halfway; | 5158 | | | 5159 | | // shift digits into position | 5160 | 12.4k | if (shift == 64) { | 5161 | 0 | am.mantissa = 0; | 5162 | 12.4k | } else { | 5163 | 12.4k | am.mantissa >>= shift; | 5164 | 12.4k | } | 5165 | 12.4k | am.power2 += shift; | 5166 | | | 5167 | 12.4k | bool is_odd = (am.mantissa & 1) == 1; | 5168 | 12.4k | am.mantissa += uint64_t(cb(is_odd, is_halfway, is_above)); | 5169 | 12.4k | } |
void fast_float::round_nearest_tie_even<fast_float::negative_digit_comp<double>(fast_float::bigint&, fast_float::adjusted_mantissa, int)::{lambda(fast_float::adjusted_mantissa&, int)#2}::operator()(fast_float::adjusted_mantissa&, int) const::{lambda(bool, bool, bool)#1}>(fast_float::adjusted_mantissa&, int, fast_float::negative_digit_comp<double>(fast_float::bigint&, fast_float::adjusted_mantissa, int)::{lambda(fast_float::adjusted_mantissa&, int)#2}::operator()(fast_float::adjusted_mantissa&, int) const::{lambda(bool, bool, bool)#1})Line | Count | Source | 5152 | 22.7k | callback cb) noexcept { | 5153 | 22.7k | uint64_t const mask = (shift == 64) ? UINT64_MAX : (uint64_t(1) << shift) - 1; | 5154 | 22.7k | uint64_t const halfway = (shift == 0) ? 0 : uint64_t(1) << (shift - 1); | 5155 | 22.7k | uint64_t truncated_bits = am.mantissa & mask; | 5156 | 22.7k | bool is_above = truncated_bits > halfway; | 5157 | 22.7k | bool is_halfway = truncated_bits == halfway; | 5158 | | | 5159 | | // shift digits into position | 5160 | 22.7k | if (shift == 64) { | 5161 | 0 | am.mantissa = 0; | 5162 | 22.7k | } else { | 5163 | 22.7k | am.mantissa >>= shift; | 5164 | 22.7k | } | 5165 | 22.7k | am.power2 += shift; | 5166 | | | 5167 | 22.7k | bool is_odd = (am.mantissa & 1) == 1; | 5168 | 22.7k | am.mantissa += uint64_t(cb(is_odd, is_halfway, is_above)); | 5169 | 22.7k | } |
Unexecuted instantiation: void fast_float::round_nearest_tie_even<fast_float::positive_digit_comp<float>(fast_float::bigint&, int)::{lambda(fast_float::adjusted_mantissa&, int)#1}::operator()(fast_float::adjusted_mantissa&, int) const::{lambda(bool, bool, bool)#1}>(fast_float::adjusted_mantissa&, int, fast_float::positive_digit_comp<float>(fast_float::bigint&, int)::{lambda(fast_float::adjusted_mantissa&, int)#1}::operator()(fast_float::adjusted_mantissa&, int) const::{lambda(bool, bool, bool)#1})Unexecuted instantiation: void fast_float::round_nearest_tie_even<fast_float::negative_digit_comp<float>(fast_float::bigint&, fast_float::adjusted_mantissa, int)::{lambda(fast_float::adjusted_mantissa&, int)#2}::operator()(fast_float::adjusted_mantissa&, int) const::{lambda(bool, bool, bool)#1}>(fast_float::adjusted_mantissa&, int, fast_float::negative_digit_comp<float>(fast_float::bigint&, fast_float::adjusted_mantissa, int)::{lambda(fast_float::adjusted_mantissa&, int)#2}::operator()(fast_float::adjusted_mantissa&, int) const::{lambda(bool, bool, bool)#1}) |
5170 | | |
5171 | | fastfloat_really_inline FASTFLOAT_CONSTEXPR14 void |
5172 | 22.7k | round_down(adjusted_mantissa &am, int32_t shift) noexcept { |
5173 | 22.7k | if (shift == 64) { |
5174 | 0 | am.mantissa = 0; |
5175 | 22.7k | } else { |
5176 | 22.7k | am.mantissa >>= shift; |
5177 | 22.7k | } |
5178 | 22.7k | am.power2 += shift; |
5179 | 22.7k | } |
5180 | | |
5181 | | template <typename UC> |
5182 | | fastfloat_really_inline FASTFLOAT_CONSTEXPR20 void |
5183 | 41.1k | skip_zeros(UC const *&first, UC const *last) noexcept { |
5184 | 41.1k | uint64_t val; |
5185 | 70.8k | while (!cpp20_and_in_constexpr() && |
5186 | 70.8k | tinyobj_ff::distance(first, last) >= int_cmp_len<UC>()) { |
5187 | 57.4k | ::memcpy(&val, first, sizeof(uint64_t)); |
5188 | 57.4k | if (val != int_cmp_zeros<UC>()) { |
5189 | 27.8k | break; |
5190 | 27.8k | } |
5191 | 29.6k | first += int_cmp_len<UC>(); |
5192 | 29.6k | } |
5193 | 66.2k | while (first != last) { |
5194 | 60.2k | if (*first != UC('0')) { |
5195 | 35.1k | break; |
5196 | 35.1k | } |
5197 | 25.0k | first++; |
5198 | 25.0k | } |
5199 | 41.1k | } |
5200 | | |
5201 | | // determine if any non-zero digits were truncated. |
5202 | | // all characters must be valid digits. |
5203 | | template <typename UC> |
5204 | | fastfloat_really_inline FASTFLOAT_CONSTEXPR20 bool |
5205 | 8.93k | is_truncated(UC const *first, UC const *last) noexcept { |
5206 | | // do 8-bit optimizations, can just compare to 8 literal 0s. |
5207 | 8.93k | uint64_t val; |
5208 | 172k | while (!cpp20_and_in_constexpr() && |
5209 | 172k | tinyobj_ff::distance(first, last) >= int_cmp_len<UC>()) { |
5210 | 167k | ::memcpy(&val, first, sizeof(uint64_t)); |
5211 | 167k | if (val != int_cmp_zeros<UC>()) { |
5212 | 3.47k | return true; |
5213 | 3.47k | } |
5214 | 163k | first += int_cmp_len<UC>(); |
5215 | 163k | } |
5216 | 24.2k | while (first != last) { |
5217 | 20.4k | if (*first != UC('0')) { |
5218 | 1.59k | return true; |
5219 | 1.59k | } |
5220 | 18.8k | ++first; |
5221 | 18.8k | } |
5222 | 3.86k | return false; |
5223 | 5.46k | } |
5224 | | |
5225 | | template <typename UC> |
5226 | | fastfloat_really_inline FASTFLOAT_CONSTEXPR20 bool |
5227 | 1.99k | is_truncated(span<UC const> s) noexcept { |
5228 | 1.99k | return is_truncated(s.ptr, s.ptr + s.len()); |
5229 | 1.99k | } |
5230 | | |
5231 | | template <typename UC> |
5232 | | fastfloat_really_inline FASTFLOAT_CONSTEXPR20 void |
5233 | | parse_eight_digits(UC const *&p, limb &value, size_t &counter, |
5234 | 763k | size_t &count) noexcept { |
5235 | 763k | value = value * 100000000 + parse_eight_digits_unrolled(p); |
5236 | 763k | p += 8; |
5237 | 763k | counter += 8; |
5238 | 763k | count += 8; |
5239 | 763k | } |
5240 | | |
5241 | | template <typename UC> |
5242 | | fastfloat_really_inline FASTFLOAT_CONSTEXPR14 void |
5243 | | parse_one_digit(UC const *&p, limb &value, size_t &counter, |
5244 | 1.23M | size_t &count) noexcept { |
5245 | 1.23M | value = value * 10 + limb(*p - UC('0')); |
5246 | 1.23M | p++; |
5247 | 1.23M | counter++; |
5248 | 1.23M | count++; |
5249 | 1.23M | } |
5250 | | |
5251 | | fastfloat_really_inline FASTFLOAT_CONSTEXPR20 void |
5252 | 420k | add_native(bigint &big, limb power, limb value) noexcept { |
5253 | 420k | big.mul(power); |
5254 | 420k | big.add(value); |
5255 | 420k | } |
5256 | | |
5257 | | fastfloat_really_inline FASTFLOAT_CONSTEXPR20 void |
5258 | 4.13k | round_up_bigint(bigint &big, size_t &count) noexcept { |
5259 | | // need to round-up the digits, but need to avoid rounding |
5260 | | // ....9999 to ...10000, which could cause a false halfway point. |
5261 | 4.13k | add_native(big, 10, 1); |
5262 | 4.13k | count++; |
5263 | 4.13k | } |
5264 | | |
5265 | | // parse the significant digits into a big integer |
5266 | | template <typename UC> |
5267 | | inline FASTFLOAT_CONSTEXPR20 void |
5268 | | parse_mantissa(bigint &result, parsed_number_string_t<UC> &num, |
5269 | 35.1k | size_t max_digits, size_t &digits) noexcept { |
5270 | | // try to minimize the number of big integer and scalar multiplication. |
5271 | | // therefore, try to parse 8 digits at a time, and multiply by the largest |
5272 | | // scalar value (9 or 19 digits) for each step. |
5273 | 35.1k | size_t counter = 0; |
5274 | 35.1k | digits = 0; |
5275 | 35.1k | limb value = 0; |
5276 | 35.1k | #ifdef FASTFLOAT_64BIT_LIMB |
5277 | 35.1k | size_t step = 19; |
5278 | | #else |
5279 | | size_t step = 9; |
5280 | | #endif |
5281 | | |
5282 | | // process all integer digits. |
5283 | 35.1k | UC const *p = num.integer.ptr; |
5284 | 35.1k | UC const *pend = p + num.integer.len(); |
5285 | 35.1k | skip_zeros(p, pend); |
5286 | | // process all digits, in increments of step per loop |
5287 | 187k | while (p != pend) { |
5288 | 423k | while ((tinyobj_ff::distance(p, pend) >= 8) && (step - counter >= 8) && |
5289 | 270k | (max_digits - digits >= 8)) { |
5290 | 268k | parse_eight_digits(p, value, counter, digits); |
5291 | 268k | } |
5292 | 613k | while (counter < step && p != pend && digits < max_digits) { |
5293 | 458k | parse_one_digit(p, value, counter, digits); |
5294 | 458k | } |
5295 | 155k | if (digits == max_digits) { |
5296 | | // add the temporary value, then check if we've truncated any digits |
5297 | 2.63k | add_native(result, limb(powers_of_ten_uint64[counter]), value); |
5298 | 2.63k | bool truncated = is_truncated(p, pend); |
5299 | 2.63k | if (num.fraction.ptr != nullptr) { |
5300 | 1.99k | truncated |= is_truncated(num.fraction); |
5301 | 1.99k | } |
5302 | 2.63k | if (truncated) { |
5303 | 1.63k | round_up_bigint(result, digits); |
5304 | 1.63k | } |
5305 | 2.63k | return; |
5306 | 152k | } else { |
5307 | 152k | add_native(result, limb(powers_of_ten_uint64[counter]), value); |
5308 | 152k | counter = 0; |
5309 | 152k | value = 0; |
5310 | 152k | } |
5311 | 155k | } |
5312 | | |
5313 | | // add our fraction digits, if they're available. |
5314 | 32.5k | if (num.fraction.ptr != nullptr) { |
5315 | 24.3k | p = num.fraction.ptr; |
5316 | 24.3k | pend = p + num.fraction.len(); |
5317 | 24.3k | if (digits == 0) { |
5318 | 5.98k | skip_zeros(p, pend); |
5319 | 5.98k | } |
5320 | | // process all digits, in increments of step per loop |
5321 | 281k | while (p != pend) { |
5322 | 756k | while ((tinyobj_ff::distance(p, pend) >= 8) && (step - counter >= 8) && |
5323 | 498k | (max_digits - digits >= 8)) { |
5324 | 495k | parse_eight_digits(p, value, counter, digits); |
5325 | 495k | } |
5326 | 1.03M | while (counter < step && p != pend && digits < max_digits) { |
5327 | 773k | parse_one_digit(p, value, counter, digits); |
5328 | 773k | } |
5329 | 261k | if (digits == max_digits) { |
5330 | | // add the temporary value, then check if we've truncated any digits |
5331 | 4.30k | add_native(result, limb(powers_of_ten_uint64[counter]), value); |
5332 | 4.30k | bool truncated = is_truncated(p, pend); |
5333 | 4.30k | if (truncated) { |
5334 | 2.50k | round_up_bigint(result, digits); |
5335 | 2.50k | } |
5336 | 4.30k | return; |
5337 | 256k | } else { |
5338 | 256k | add_native(result, limb(powers_of_ten_uint64[counter]), value); |
5339 | 256k | counter = 0; |
5340 | 256k | value = 0; |
5341 | 256k | } |
5342 | 261k | } |
5343 | 24.3k | } |
5344 | | |
5345 | 28.2k | if (counter != 0) { |
5346 | 0 | add_native(result, limb(powers_of_ten_uint64[counter]), value); |
5347 | 0 | } |
5348 | 28.2k | } |
5349 | | |
5350 | | template <typename T> |
5351 | | inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa |
5352 | 12.4k | positive_digit_comp(bigint &bigmant, int32_t exponent) noexcept { |
5353 | 12.4k | FASTFLOAT_ASSERT(bigmant.pow10(uint32_t(exponent))); |
5354 | 12.4k | adjusted_mantissa answer; |
5355 | 12.4k | bool truncated; |
5356 | 12.4k | answer.mantissa = bigmant.hi64(truncated); |
5357 | 12.4k | int bias = binary_format<T>::mantissa_explicit_bits() - |
5358 | 12.4k | binary_format<T>::minimum_exponent(); |
5359 | 12.4k | answer.power2 = bigmant.bit_length() - 64 + bias; |
5360 | | |
5361 | 12.4k | round<T>(answer, [truncated](adjusted_mantissa &a, int32_t shift) { |
5362 | 12.4k | round_nearest_tie_even( |
5363 | 12.4k | a, shift, |
5364 | 12.4k | [truncated](bool is_odd, bool is_halfway, bool is_above) -> bool { |
5365 | 12.4k | return is_above || (is_halfway && truncated) || |
5366 | 4.08k | (is_odd && is_halfway); |
5367 | 12.4k | }); fast_float::positive_digit_comp<double>(fast_float::bigint&, int)::{lambda(fast_float::adjusted_mantissa&, int)#1}::operator()(fast_float::adjusted_mantissa&, int) const::{lambda(bool, bool, bool)#1}::operator()(bool, bool, bool) constLine | Count | Source | 5364 | 12.4k | [truncated](bool is_odd, bool is_halfway, bool is_above) -> bool { | 5365 | 12.4k | return is_above || (is_halfway && truncated) || | 5366 | 4.08k | (is_odd && is_halfway); | 5367 | 12.4k | }); |
Unexecuted instantiation: fast_float::positive_digit_comp<float>(fast_float::bigint&, int)::{lambda(fast_float::adjusted_mantissa&, int)#1}::operator()(fast_float::adjusted_mantissa&, int) const::{lambda(bool, bool, bool)#1}::operator()(bool, bool, bool) const |
5368 | 12.4k | }); fast_float::positive_digit_comp<double>(fast_float::bigint&, int)::{lambda(fast_float::adjusted_mantissa&, int)#1}::operator()(fast_float::adjusted_mantissa&, int) constLine | Count | Source | 5361 | 12.4k | round<T>(answer, [truncated](adjusted_mantissa &a, int32_t shift) { | 5362 | 12.4k | round_nearest_tie_even( | 5363 | 12.4k | a, shift, | 5364 | 12.4k | [truncated](bool is_odd, bool is_halfway, bool is_above) -> bool { | 5365 | 12.4k | return is_above || (is_halfway && truncated) || | 5366 | 12.4k | (is_odd && is_halfway); | 5367 | 12.4k | }); | 5368 | 12.4k | }); |
Unexecuted instantiation: fast_float::positive_digit_comp<float>(fast_float::bigint&, int)::{lambda(fast_float::adjusted_mantissa&, int)#1}::operator()(fast_float::adjusted_mantissa&, int) const |
5369 | | |
5370 | 12.4k | return answer; |
5371 | 12.4k | } fast_float::adjusted_mantissa fast_float::positive_digit_comp<double>(fast_float::bigint&, int) Line | Count | Source | 5352 | 12.4k | positive_digit_comp(bigint &bigmant, int32_t exponent) noexcept { | 5353 | 12.4k | FASTFLOAT_ASSERT(bigmant.pow10(uint32_t(exponent))); | 5354 | 12.4k | adjusted_mantissa answer; | 5355 | 12.4k | bool truncated; | 5356 | 12.4k | answer.mantissa = bigmant.hi64(truncated); | 5357 | 12.4k | int bias = binary_format<T>::mantissa_explicit_bits() - | 5358 | 12.4k | binary_format<T>::minimum_exponent(); | 5359 | 12.4k | answer.power2 = bigmant.bit_length() - 64 + bias; | 5360 | | | 5361 | 12.4k | round<T>(answer, [truncated](adjusted_mantissa &a, int32_t shift) { | 5362 | 12.4k | round_nearest_tie_even( | 5363 | 12.4k | a, shift, | 5364 | 12.4k | [truncated](bool is_odd, bool is_halfway, bool is_above) -> bool { | 5365 | 12.4k | return is_above || (is_halfway && truncated) || | 5366 | 12.4k | (is_odd && is_halfway); | 5367 | 12.4k | }); | 5368 | 12.4k | }); | 5369 | | | 5370 | 12.4k | return answer; | 5371 | 12.4k | } |
Unexecuted instantiation: fast_float::adjusted_mantissa fast_float::positive_digit_comp<float>(fast_float::bigint&, int) |
5372 | | |
5373 | | // the scaling here is quite simple: we have, for the real digits `m * 10^e`, |
5374 | | // and for the theoretical digits `n * 2^f`. Since `e` is always negative, |
5375 | | // to scale them identically, we do `n * 2^f * 5^-f`, so we now have `m * 2^e`. |
5376 | | // we then need to scale by `2^(f- e)`, and then the two significant digits |
5377 | | // are of the same magnitude. |
5378 | | template <typename T> |
5379 | | inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa negative_digit_comp( |
5380 | 22.7k | bigint &bigmant, adjusted_mantissa am, int32_t exponent) noexcept { |
5381 | 22.7k | bigint &real_digits = bigmant; |
5382 | 22.7k | int32_t real_exp = exponent; |
5383 | | |
5384 | | // get the value of `b`, rounded down, and get a bigint representation of b+h |
5385 | 22.7k | adjusted_mantissa am_b = am; |
5386 | | // gcc7 buf: use a lambda to remove the noexcept qualifier bug with |
5387 | | // -Wnoexcept-type. |
5388 | 22.7k | round<T>(am_b, |
5389 | 22.7k | [](adjusted_mantissa &a, int32_t shift) { round_down(a, shift); });fast_float::negative_digit_comp<double>(fast_float::bigint&, fast_float::adjusted_mantissa, int)::{lambda(fast_float::adjusted_mantissa&, int)#1}::operator()(fast_float::adjusted_mantissa&, int) constLine | Count | Source | 5389 | 22.7k | [](adjusted_mantissa &a, int32_t shift) { round_down(a, shift); }); |
Unexecuted instantiation: fast_float::negative_digit_comp<float>(fast_float::bigint&, fast_float::adjusted_mantissa, int)::{lambda(fast_float::adjusted_mantissa&, int)#1}::operator()(fast_float::adjusted_mantissa&, int) const |
5390 | 22.7k | T b; |
5391 | 22.7k | to_float(false, am_b, b); |
5392 | 22.7k | adjusted_mantissa theor = to_extended_halfway(b); |
5393 | 22.7k | bigint theor_digits(theor.mantissa); |
5394 | 22.7k | int32_t theor_exp = theor.power2; |
5395 | | |
5396 | | // scale real digits and theor digits to be same power. |
5397 | 22.7k | int32_t pow2_exp = theor_exp - real_exp; |
5398 | 22.7k | uint32_t pow5_exp = uint32_t(-real_exp); |
5399 | 22.7k | if (pow5_exp != 0) { |
5400 | 22.7k | FASTFLOAT_ASSERT(theor_digits.pow5(pow5_exp)); |
5401 | 22.7k | } |
5402 | 22.7k | if (pow2_exp > 0) { |
5403 | 13.2k | FASTFLOAT_ASSERT(theor_digits.pow2(uint32_t(pow2_exp))); |
5404 | 13.2k | } else if (pow2_exp < 0) { |
5405 | 9.17k | FASTFLOAT_ASSERT(real_digits.pow2(uint32_t(-pow2_exp))); |
5406 | 9.17k | } |
5407 | | |
5408 | | // compare digits, and use it to director rounding |
5409 | 22.7k | int ord = real_digits.compare(theor_digits); |
5410 | 22.7k | adjusted_mantissa answer = am; |
5411 | 22.7k | round<T>(answer, [ord](adjusted_mantissa &a, int32_t shift) { |
5412 | 22.7k | round_nearest_tie_even( |
5413 | 22.7k | a, shift, [ord](bool is_odd, bool _, bool __) -> bool { |
5414 | 22.7k | (void)_; // not needed, since we've done our comparison |
5415 | 22.7k | (void)__; // not needed, since we've done our comparison |
5416 | 22.7k | if (ord > 0) { |
5417 | 6.63k | return true; |
5418 | 16.0k | } else if (ord < 0) { |
5419 | 14.9k | return false; |
5420 | 14.9k | } else { |
5421 | 1.08k | return is_odd; |
5422 | 1.08k | } |
5423 | 22.7k | }); fast_float::negative_digit_comp<double>(fast_float::bigint&, fast_float::adjusted_mantissa, int)::{lambda(fast_float::adjusted_mantissa&, int)#2}::operator()(fast_float::adjusted_mantissa&, int) const::{lambda(bool, bool, bool)#1}::operator()(bool, bool, bool) constLine | Count | Source | 5413 | 22.7k | a, shift, [ord](bool is_odd, bool _, bool __) -> bool { | 5414 | 22.7k | (void)_; // not needed, since we've done our comparison | 5415 | 22.7k | (void)__; // not needed, since we've done our comparison | 5416 | 22.7k | if (ord > 0) { | 5417 | 6.63k | return true; | 5418 | 16.0k | } else if (ord < 0) { | 5419 | 14.9k | return false; | 5420 | 14.9k | } else { | 5421 | 1.08k | return is_odd; | 5422 | 1.08k | } | 5423 | 22.7k | }); |
Unexecuted instantiation: fast_float::negative_digit_comp<float>(fast_float::bigint&, fast_float::adjusted_mantissa, int)::{lambda(fast_float::adjusted_mantissa&, int)#2}::operator()(fast_float::adjusted_mantissa&, int) const::{lambda(bool, bool, bool)#1}::operator()(bool, bool, bool) const |
5424 | 22.7k | }); fast_float::negative_digit_comp<double>(fast_float::bigint&, fast_float::adjusted_mantissa, int)::{lambda(fast_float::adjusted_mantissa&, int)#2}::operator()(fast_float::adjusted_mantissa&, int) constLine | Count | Source | 5411 | 22.7k | round<T>(answer, [ord](adjusted_mantissa &a, int32_t shift) { | 5412 | 22.7k | round_nearest_tie_even( | 5413 | 22.7k | a, shift, [ord](bool is_odd, bool _, bool __) -> bool { | 5414 | 22.7k | (void)_; // not needed, since we've done our comparison | 5415 | 22.7k | (void)__; // not needed, since we've done our comparison | 5416 | 22.7k | if (ord > 0) { | 5417 | 22.7k | return true; | 5418 | 22.7k | } else if (ord < 0) { | 5419 | 22.7k | return false; | 5420 | 22.7k | } else { | 5421 | 22.7k | return is_odd; | 5422 | 22.7k | } | 5423 | 22.7k | }); | 5424 | 22.7k | }); |
Unexecuted instantiation: fast_float::negative_digit_comp<float>(fast_float::bigint&, fast_float::adjusted_mantissa, int)::{lambda(fast_float::adjusted_mantissa&, int)#2}::operator()(fast_float::adjusted_mantissa&, int) const |
5425 | | |
5426 | 22.7k | return answer; |
5427 | 22.7k | } fast_float::adjusted_mantissa fast_float::negative_digit_comp<double>(fast_float::bigint&, fast_float::adjusted_mantissa, int) Line | Count | Source | 5380 | 22.7k | bigint &bigmant, adjusted_mantissa am, int32_t exponent) noexcept { | 5381 | 22.7k | bigint &real_digits = bigmant; | 5382 | 22.7k | int32_t real_exp = exponent; | 5383 | | | 5384 | | // get the value of `b`, rounded down, and get a bigint representation of b+h | 5385 | 22.7k | adjusted_mantissa am_b = am; | 5386 | | // gcc7 buf: use a lambda to remove the noexcept qualifier bug with | 5387 | | // -Wnoexcept-type. | 5388 | 22.7k | round<T>(am_b, | 5389 | 22.7k | [](adjusted_mantissa &a, int32_t shift) { round_down(a, shift); }); | 5390 | 22.7k | T b; | 5391 | 22.7k | to_float(false, am_b, b); | 5392 | 22.7k | adjusted_mantissa theor = to_extended_halfway(b); | 5393 | 22.7k | bigint theor_digits(theor.mantissa); | 5394 | 22.7k | int32_t theor_exp = theor.power2; | 5395 | | | 5396 | | // scale real digits and theor digits to be same power. | 5397 | 22.7k | int32_t pow2_exp = theor_exp - real_exp; | 5398 | 22.7k | uint32_t pow5_exp = uint32_t(-real_exp); | 5399 | 22.7k | if (pow5_exp != 0) { | 5400 | 22.7k | FASTFLOAT_ASSERT(theor_digits.pow5(pow5_exp)); | 5401 | 22.7k | } | 5402 | 22.7k | if (pow2_exp > 0) { | 5403 | 13.2k | FASTFLOAT_ASSERT(theor_digits.pow2(uint32_t(pow2_exp))); | 5404 | 13.2k | } else if (pow2_exp < 0) { | 5405 | 9.17k | FASTFLOAT_ASSERT(real_digits.pow2(uint32_t(-pow2_exp))); | 5406 | 9.17k | } | 5407 | | | 5408 | | // compare digits, and use it to director rounding | 5409 | 22.7k | int ord = real_digits.compare(theor_digits); | 5410 | 22.7k | adjusted_mantissa answer = am; | 5411 | 22.7k | round<T>(answer, [ord](adjusted_mantissa &a, int32_t shift) { | 5412 | 22.7k | round_nearest_tie_even( | 5413 | 22.7k | a, shift, [ord](bool is_odd, bool _, bool __) -> bool { | 5414 | 22.7k | (void)_; // not needed, since we've done our comparison | 5415 | 22.7k | (void)__; // not needed, since we've done our comparison | 5416 | 22.7k | if (ord > 0) { | 5417 | 22.7k | return true; | 5418 | 22.7k | } else if (ord < 0) { | 5419 | 22.7k | return false; | 5420 | 22.7k | } else { | 5421 | 22.7k | return is_odd; | 5422 | 22.7k | } | 5423 | 22.7k | }); | 5424 | 22.7k | }); | 5425 | | | 5426 | 22.7k | return answer; | 5427 | 22.7k | } |
Unexecuted instantiation: fast_float::adjusted_mantissa fast_float::negative_digit_comp<float>(fast_float::bigint&, fast_float::adjusted_mantissa, int) |
5428 | | |
5429 | | // parse the significant digits as a big integer to unambiguously round the |
5430 | | // the significant digits. here, we are trying to determine how to round |
5431 | | // an extended float representation close to `b+h`, halfway between `b` |
5432 | | // (the float rounded-down) and `b+u`, the next positive float. this |
5433 | | // algorithm is always correct, and uses one of two approaches. when |
5434 | | // the exponent is positive relative to the significant digits (such as |
5435 | | // 1234), we create a big-integer representation, get the high 64-bits, |
5436 | | // determine if any lower bits are truncated, and use that to direct |
5437 | | // rounding. in case of a negative exponent relative to the significant |
5438 | | // digits (such as 1.2345), we create a theoretical representation of |
5439 | | // `b` as a big-integer type, scaled to the same binary exponent as |
5440 | | // the actual digits. we then compare the big integer representations |
5441 | | // of both, and use that to direct rounding. |
5442 | | template <typename T, typename UC> |
5443 | | inline FASTFLOAT_CONSTEXPR20 adjusted_mantissa |
5444 | 35.1k | digit_comp(parsed_number_string_t<UC> &num, adjusted_mantissa am) noexcept { |
5445 | | // remove the invalid exponent bias |
5446 | 35.1k | am.power2 -= invalid_am_bias; |
5447 | | |
5448 | 35.1k | int32_t sci_exp = scientific_exponent(num); |
5449 | 35.1k | size_t max_digits = binary_format<T>::max_digits(); |
5450 | 35.1k | size_t digits = 0; |
5451 | 35.1k | bigint bigmant; |
5452 | 35.1k | parse_mantissa(bigmant, num, max_digits, digits); |
5453 | | // can't underflow, since digits is at most max_digits. |
5454 | 35.1k | int32_t exponent = sci_exp + 1 - int32_t(digits); |
5455 | 35.1k | if (exponent >= 0) { |
5456 | 12.4k | return positive_digit_comp<T>(bigmant, exponent); |
5457 | 22.7k | } else { |
5458 | 22.7k | return negative_digit_comp<T>(bigmant, am, exponent); |
5459 | 22.7k | } |
5460 | 35.1k | } fast_float::adjusted_mantissa fast_float::digit_comp<double, char>(fast_float::parsed_number_string_t<char>&, fast_float::adjusted_mantissa) Line | Count | Source | 5444 | 35.1k | digit_comp(parsed_number_string_t<UC> &num, adjusted_mantissa am) noexcept { | 5445 | | // remove the invalid exponent bias | 5446 | 35.1k | am.power2 -= invalid_am_bias; | 5447 | | | 5448 | 35.1k | int32_t sci_exp = scientific_exponent(num); | 5449 | 35.1k | size_t max_digits = binary_format<T>::max_digits(); | 5450 | 35.1k | size_t digits = 0; | 5451 | 35.1k | bigint bigmant; | 5452 | 35.1k | parse_mantissa(bigmant, num, max_digits, digits); | 5453 | | // can't underflow, since digits is at most max_digits. | 5454 | 35.1k | int32_t exponent = sci_exp + 1 - int32_t(digits); | 5455 | 35.1k | if (exponent >= 0) { | 5456 | 12.4k | return positive_digit_comp<T>(bigmant, exponent); | 5457 | 22.7k | } else { | 5458 | 22.7k | return negative_digit_comp<T>(bigmant, am, exponent); | 5459 | 22.7k | } | 5460 | 35.1k | } |
Unexecuted instantiation: fast_float::adjusted_mantissa fast_float::digit_comp<float, char>(fast_float::parsed_number_string_t<char>&, fast_float::adjusted_mantissa) |
5461 | | |
5462 | | } // namespace fast_float |
5463 | | |
5464 | | #endif |
5465 | | |
5466 | | #ifndef FASTFLOAT_PARSE_NUMBER_H |
5467 | | #define FASTFLOAT_PARSE_NUMBER_H |
5468 | | |
5469 | | |
5470 | | #include <cmath> |
5471 | | #include <cstring> |
5472 | | #include <limits> |
5473 | | |
5474 | | namespace fast_float { |
5475 | | |
5476 | | namespace detail { |
5477 | | /** |
5478 | | * Special case +inf, -inf, nan, infinity, -infinity. |
5479 | | * The case comparisons could be made much faster given that we know that the |
5480 | | * strings a null-free and fixed. |
5481 | | **/ |
5482 | | template <typename T, typename UC> |
5483 | | from_chars_result_t<UC> |
5484 | | FASTFLOAT_CONSTEXPR14 parse_infnan(UC const *first, UC const *last, |
5485 | 142k | T &value, chars_format fmt) noexcept { |
5486 | 142k | from_chars_result_t<UC> answer{}; |
5487 | 142k | answer.ptr = first; |
5488 | 142k | answer.ec = tinyobj_ff::ff_errc(); // be optimistic |
5489 | | // assume first < last, so dereference without checks; |
5490 | 142k | bool const minusSign = (*first == UC('-')); |
5491 | | // C++17 20.19.3.(7.1) explicitly forbids '+' sign here |
5492 | 142k | if ((*first == UC('-')) || |
5493 | 111k | (uint64_t(fmt & chars_format::allow_leading_plus) && |
5494 | 111k | (*first == UC('+')))) { |
5495 | 39.9k | ++first; |
5496 | 39.9k | } |
5497 | 142k | if (last - first >= 3) { |
5498 | 65.1k | if (fastfloat_strncasecmp(first, str_const_nan<UC>(), 3)) { |
5499 | 0 | answer.ptr = (first += 3); |
5500 | 0 | value = minusSign ? -std::numeric_limits<T>::quiet_NaN() |
5501 | 0 | : std::numeric_limits<T>::quiet_NaN(); |
5502 | | // Check for possible nan(n-char-seq-opt), C++17 20.19.3.7, |
5503 | | // C11 7.20.1.3.3. At least MSVC produces nan(ind) and nan(snan). |
5504 | 0 | if (first != last && *first == UC('(')) { |
5505 | 0 | for (UC const *ptr = first + 1; ptr != last; ++ptr) { |
5506 | 0 | if (*ptr == UC(')')) { |
5507 | 0 | answer.ptr = ptr + 1; // valid nan(n-char-seq-opt) |
5508 | 0 | break; |
5509 | 0 | } else if (!((UC('a') <= *ptr && *ptr <= UC('z')) || |
5510 | 0 | (UC('A') <= *ptr && *ptr <= UC('Z')) || |
5511 | 0 | (UC('0') <= *ptr && *ptr <= UC('9')) || *ptr == UC('_'))) |
5512 | 0 | break; // forbidden char, not nan(n-char-seq-opt) |
5513 | 0 | } |
5514 | 0 | } |
5515 | 0 | return answer; |
5516 | 0 | } |
5517 | 65.1k | if (fastfloat_strncasecmp(first, str_const_inf<UC>(), 3)) { |
5518 | 0 | if ((last - first >= 8) && |
5519 | 0 | fastfloat_strncasecmp(first + 3, str_const_inf<UC>() + 3, 5)) { |
5520 | 0 | answer.ptr = first + 8; |
5521 | 0 | } else { |
5522 | 0 | answer.ptr = first + 3; |
5523 | 0 | } |
5524 | 0 | value = minusSign ? -std::numeric_limits<T>::infinity() |
5525 | 0 | : std::numeric_limits<T>::infinity(); |
5526 | 0 | return answer; |
5527 | 0 | } |
5528 | 65.1k | } |
5529 | 142k | answer.ec = tinyobj_ff::ff_errc::invalid_argument; |
5530 | 142k | return answer; |
5531 | 142k | } fast_float::from_chars_result_t<char> fast_float::detail::parse_infnan<double, char>(char const*, char const*, double&, fast_float::chars_format) Line | Count | Source | 5485 | 142k | T &value, chars_format fmt) noexcept { | 5486 | 142k | from_chars_result_t<UC> answer{}; | 5487 | 142k | answer.ptr = first; | 5488 | 142k | answer.ec = tinyobj_ff::ff_errc(); // be optimistic | 5489 | | // assume first < last, so dereference without checks; | 5490 | 142k | bool const minusSign = (*first == UC('-')); | 5491 | | // C++17 20.19.3.(7.1) explicitly forbids '+' sign here | 5492 | 142k | if ((*first == UC('-')) || | 5493 | 111k | (uint64_t(fmt & chars_format::allow_leading_plus) && | 5494 | 111k | (*first == UC('+')))) { | 5495 | 39.9k | ++first; | 5496 | 39.9k | } | 5497 | 142k | if (last - first >= 3) { | 5498 | 65.1k | if (fastfloat_strncasecmp(first, str_const_nan<UC>(), 3)) { | 5499 | 0 | answer.ptr = (first += 3); | 5500 | 0 | value = minusSign ? -std::numeric_limits<T>::quiet_NaN() | 5501 | 0 | : std::numeric_limits<T>::quiet_NaN(); | 5502 | | // Check for possible nan(n-char-seq-opt), C++17 20.19.3.7, | 5503 | | // C11 7.20.1.3.3. At least MSVC produces nan(ind) and nan(snan). | 5504 | 0 | if (first != last && *first == UC('(')) { | 5505 | 0 | for (UC const *ptr = first + 1; ptr != last; ++ptr) { | 5506 | 0 | if (*ptr == UC(')')) { | 5507 | 0 | answer.ptr = ptr + 1; // valid nan(n-char-seq-opt) | 5508 | 0 | break; | 5509 | 0 | } else if (!((UC('a') <= *ptr && *ptr <= UC('z')) || | 5510 | 0 | (UC('A') <= *ptr && *ptr <= UC('Z')) || | 5511 | 0 | (UC('0') <= *ptr && *ptr <= UC('9')) || *ptr == UC('_'))) | 5512 | 0 | break; // forbidden char, not nan(n-char-seq-opt) | 5513 | 0 | } | 5514 | 0 | } | 5515 | 0 | return answer; | 5516 | 0 | } | 5517 | 65.1k | if (fastfloat_strncasecmp(first, str_const_inf<UC>(), 3)) { | 5518 | 0 | if ((last - first >= 8) && | 5519 | 0 | fastfloat_strncasecmp(first + 3, str_const_inf<UC>() + 3, 5)) { | 5520 | 0 | answer.ptr = first + 8; | 5521 | 0 | } else { | 5522 | 0 | answer.ptr = first + 3; | 5523 | 0 | } | 5524 | 0 | value = minusSign ? -std::numeric_limits<T>::infinity() | 5525 | 0 | : std::numeric_limits<T>::infinity(); | 5526 | 0 | return answer; | 5527 | 0 | } | 5528 | 65.1k | } | 5529 | 142k | answer.ec = tinyobj_ff::ff_errc::invalid_argument; | 5530 | 142k | return answer; | 5531 | 142k | } |
Unexecuted instantiation: fast_float::from_chars_result_t<char> fast_float::detail::parse_infnan<float, char>(char const*, char const*, float&, fast_float::chars_format) |
5532 | | |
5533 | | /** |
5534 | | * Returns true if the floating-pointing rounding mode is to 'nearest'. |
5535 | | * It is the default on most system. This function is meant to be inexpensive. |
5536 | | * Credit : @mwalcott3 |
5537 | | */ |
5538 | 1.66M | fastfloat_really_inline bool rounds_to_nearest() noexcept { |
5539 | | // https://lemire.me/blog/2020/06/26/gcc-not-nearest/ |
5540 | | #if (FLT_EVAL_METHOD != 1) && (FLT_EVAL_METHOD != 0) |
5541 | | return false; |
5542 | | #endif |
5543 | | // See |
5544 | | // A fast function to check your floating-point rounding mode |
5545 | | // https://lemire.me/blog/2022/11/16/a-fast-function-to-check-your-floating-point-rounding-mode/ |
5546 | | // |
5547 | | // This function is meant to be equivalent to : |
5548 | | // prior: #include <cfenv> |
5549 | | // return fegetround() == FE_TONEAREST; |
5550 | | // However, it is expected to be much faster than the fegetround() |
5551 | | // function call. |
5552 | | // |
5553 | | // The volatile keyword prevents the compiler from computing the function |
5554 | | // at compile-time. |
5555 | | // There might be other ways to prevent compile-time optimizations (e.g., |
5556 | | // asm). The value does not need to be std::numeric_limits<float>::min(), any |
5557 | | // small value so that 1 + x should round to 1 would do (after accounting for |
5558 | | // excess precision, as in 387 instructions). |
5559 | 1.66M | static float volatile fmin = std::numeric_limits<float>::min(); |
5560 | 1.66M | float fmini = fmin; // we copy it so that it gets loaded at most once. |
5561 | | // |
5562 | | // Explanation: |
5563 | | // Only when fegetround() == FE_TONEAREST do we have that |
5564 | | // fmin + 1.0f == 1.0f - fmin. |
5565 | | // |
5566 | | // FE_UPWARD: |
5567 | | // fmin + 1.0f > 1 |
5568 | | // 1.0f - fmin == 1 |
5569 | | // |
5570 | | // FE_DOWNWARD or FE_TOWARDZERO: |
5571 | | // fmin + 1.0f == 1 |
5572 | | // 1.0f - fmin < 1 |
5573 | | // |
5574 | | // Note: This may fail to be accurate if fast-math has been |
5575 | | // enabled, as rounding conventions may not apply. |
5576 | | #ifdef FASTFLOAT_VISUAL_STUDIO |
5577 | | #pragma warning(push) |
5578 | | // todo: is there a VS warning? |
5579 | | // see |
5580 | | // https://stackoverflow.com/questions/46079446/is-there-a-warning-for-floating-point-equality-checking-in-visual-studio-2013 |
5581 | | #elif defined(__clang__) |
5582 | | #pragma clang diagnostic push |
5583 | 1.66M | #pragma clang diagnostic ignored "-Wfloat-equal" |
5584 | | #elif defined(__GNUC__) |
5585 | | #pragma GCC diagnostic push |
5586 | | #pragma GCC diagnostic ignored "-Wfloat-equal" |
5587 | | #endif |
5588 | 1.66M | return (fmini + 1.0f == 1.0f - fmini); |
5589 | | #ifdef FASTFLOAT_VISUAL_STUDIO |
5590 | | #pragma warning(pop) |
5591 | | #elif defined(__clang__) |
5592 | | #pragma clang diagnostic pop |
5593 | | #elif defined(__GNUC__) |
5594 | | #pragma GCC diagnostic pop |
5595 | | #endif |
5596 | 1.66M | } |
5597 | | |
5598 | | } // namespace detail |
5599 | | |
5600 | | template <typename T> struct from_chars_caller { |
5601 | | template <typename UC> |
5602 | | FASTFLOAT_CONSTEXPR20 static from_chars_result_t<UC> |
5603 | | call(UC const *first, UC const *last, T &value, |
5604 | 1.87M | parse_options_t<UC> options) noexcept { |
5605 | 1.87M | return from_chars_advanced(first, last, value, options); |
5606 | 1.87M | } fast_float::from_chars_result_t<char> fast_float::from_chars_caller<double>::call<char>(char const*, char const*, double&, fast_float::parse_options_t<char>) Line | Count | Source | 5604 | 1.87M | parse_options_t<UC> options) noexcept { | 5605 | 1.87M | return from_chars_advanced(first, last, value, options); | 5606 | 1.87M | } |
Unexecuted instantiation: fast_float::from_chars_result_t<char> fast_float::from_chars_caller<float>::call<char>(char const*, char const*, float&, fast_float::parse_options_t<char>) |
5607 | | }; |
5608 | | |
5609 | | #ifdef __STDCPP_FLOAT32_T__ |
5610 | | template <> struct from_chars_caller<std::float32_t> { |
5611 | | template <typename UC> |
5612 | | FASTFLOAT_CONSTEXPR20 static from_chars_result_t<UC> |
5613 | | call(UC const *first, UC const *last, std::float32_t &value, |
5614 | | parse_options_t<UC> options) noexcept { |
5615 | | // if std::float32_t is defined, and we are in C++23 mode; macro set for |
5616 | | // float32; set value to float due to equivalence between float and |
5617 | | // float32_t |
5618 | | float val; |
5619 | | auto ret = from_chars_advanced(first, last, val, options); |
5620 | | value = val; |
5621 | | return ret; |
5622 | | } |
5623 | | }; |
5624 | | #endif |
5625 | | |
5626 | | #ifdef __STDCPP_FLOAT64_T__ |
5627 | | template <> struct from_chars_caller<std::float64_t> { |
5628 | | template <typename UC> |
5629 | | FASTFLOAT_CONSTEXPR20 static from_chars_result_t<UC> |
5630 | | call(UC const *first, UC const *last, std::float64_t &value, |
5631 | | parse_options_t<UC> options) noexcept { |
5632 | | // if std::float64_t is defined, and we are in C++23 mode; macro set for |
5633 | | // float64; set value as double due to equivalence between double and |
5634 | | // float64_t |
5635 | | double val; |
5636 | | auto ret = from_chars_advanced(first, last, val, options); |
5637 | | value = val; |
5638 | | return ret; |
5639 | | } |
5640 | | }; |
5641 | | #endif |
5642 | | |
5643 | | template <typename T, typename UC, typename> |
5644 | | FASTFLOAT_CONSTEXPR20 from_chars_result_t<UC> |
5645 | | from_chars(UC const *first, UC const *last, T &value, |
5646 | 1.87M | chars_format fmt /*= chars_format::general*/) noexcept { |
5647 | 1.87M | return from_chars_caller<T>::call(first, last, value, |
5648 | 1.87M | parse_options_t<UC>(fmt)); |
5649 | 1.87M | } fast_float::from_chars_result_t<char> fast_float::from_chars<double, char, int>(char const*, char const*, double&, fast_float::chars_format) Line | Count | Source | 5646 | 1.87M | chars_format fmt /*= chars_format::general*/) noexcept { | 5647 | 1.87M | return from_chars_caller<T>::call(first, last, value, | 5648 | 1.87M | parse_options_t<UC>(fmt)); | 5649 | 1.87M | } |
Unexecuted instantiation: fast_float::from_chars_result_t<char> fast_float::from_chars<float, char, int>(char const*, char const*, float&, fast_float::chars_format) |
5650 | | |
5651 | | /** |
5652 | | * This function overload takes parsed_number_string_t structure that is created |
5653 | | * and populated either by from_chars_advanced function taking chars range and |
5654 | | * parsing options or other parsing custom function implemented by user. |
5655 | | */ |
5656 | | template <typename T, typename UC> |
5657 | | FASTFLOAT_CONSTEXPR20 from_chars_result_t<UC> |
5658 | 1.73M | from_chars_advanced(parsed_number_string_t<UC> &pns, T &value) noexcept { |
5659 | | |
5660 | 1.73M | static_assert(is_supported_float_type<T>::value, |
5661 | 1.73M | "only some floating-point types are supported"); |
5662 | 1.73M | static_assert(is_supported_char_type<UC>::value, |
5663 | 1.73M | "only char, wchar_t, char16_t and char32_t are supported"); |
5664 | | |
5665 | 1.73M | from_chars_result_t<UC> answer; |
5666 | | |
5667 | 1.73M | answer.ec = tinyobj_ff::ff_errc(); // be optimistic |
5668 | 1.73M | answer.ptr = pns.lastmatch; |
5669 | | // The implementation of the Clinger's fast path is convoluted because |
5670 | | // we want round-to-nearest in all cases, irrespective of the rounding mode |
5671 | | // selected on the thread. |
5672 | | // We proceed optimistically, assuming that detail::rounds_to_nearest() |
5673 | | // returns true. |
5674 | 1.73M | if (binary_format<T>::min_exponent_fast_path() <= pns.exponent && |
5675 | 1.71M | pns.exponent <= binary_format<T>::max_exponent_fast_path() && |
5676 | 1.70M | !pns.too_many_digits) { |
5677 | | // Unfortunately, the conventional Clinger's fast path is only possible |
5678 | | // when the system rounds to the nearest float. |
5679 | | // |
5680 | | // We expect the next branch to almost always be selected. |
5681 | | // We could check it first (before the previous branch), but |
5682 | | // there might be performance advantages at having the check |
5683 | | // be last. |
5684 | 1.66M | if (!cpp20_and_in_constexpr() && detail::rounds_to_nearest()) { |
5685 | | // We have that fegetround() == FE_TONEAREST. |
5686 | | // Next is Clinger's fast path. |
5687 | 1.66M | if (pns.mantissa <= binary_format<T>::max_mantissa_fast_path()) { |
5688 | 1.65M | value = T(pns.mantissa); |
5689 | 1.65M | if (pns.exponent < 0) { |
5690 | 29.0k | value = value / binary_format<T>::exact_power_of_ten(-pns.exponent); |
5691 | 1.63M | } else { |
5692 | 1.63M | value = value * binary_format<T>::exact_power_of_ten(pns.exponent); |
5693 | 1.63M | } |
5694 | 1.65M | if (pns.negative) { |
5695 | 4.18k | value = -value; |
5696 | 4.18k | } |
5697 | 1.65M | return answer; |
5698 | 1.65M | } |
5699 | 1.66M | } else { |
5700 | | // We do not have that fegetround() == FE_TONEAREST. |
5701 | | // Next is a modified Clinger's fast path, inspired by Jakub JelÃnek's |
5702 | | // proposal |
5703 | 0 | if (pns.exponent >= 0 && |
5704 | 0 | pns.mantissa <= |
5705 | 0 | binary_format<T>::max_mantissa_fast_path(pns.exponent)) { |
5706 | 0 | #if defined(__clang__) || defined(FASTFLOAT_32BIT) |
5707 | | // Clang may map 0 to -0.0 when fegetround() == FE_DOWNWARD |
5708 | 0 | if (pns.mantissa == 0) { |
5709 | 0 | value = pns.negative ? T(-0.) : T(0.); |
5710 | 0 | return answer; |
5711 | 0 | } |
5712 | 0 | #endif |
5713 | 0 | value = T(pns.mantissa) * |
5714 | 0 | binary_format<T>::exact_power_of_ten(pns.exponent); |
5715 | 0 | if (pns.negative) { |
5716 | 0 | value = -value; |
5717 | 0 | } |
5718 | 0 | return answer; |
5719 | 0 | } |
5720 | 0 | } |
5721 | 1.66M | } |
5722 | 74.6k | adjusted_mantissa am = |
5723 | 74.6k | compute_float<binary_format<T>>(pns.exponent, pns.mantissa); |
5724 | 74.6k | if (pns.too_many_digits && am.power2 >= 0) { |
5725 | 68.9k | if (am != compute_float<binary_format<T>>(pns.exponent, pns.mantissa + 1)) { |
5726 | 35.1k | am = compute_error<binary_format<T>>(pns.exponent, pns.mantissa); |
5727 | 35.1k | } |
5728 | 68.9k | } |
5729 | | // If we called compute_float<binary_format<T>>(pns.exponent, pns.mantissa) |
5730 | | // and we have an invalid power (am.power2 < 0), then we need to go the long |
5731 | | // way around again. This is very uncommon. |
5732 | 74.6k | if (am.power2 < 0) { |
5733 | 35.1k | am = digit_comp<T>(pns, am); |
5734 | 35.1k | } |
5735 | 74.6k | to_float(pns.negative, am, value); |
5736 | | // Test for over/underflow. |
5737 | 74.6k | if ((pns.mantissa != 0 && am.mantissa == 0 && am.power2 == 0) || |
5738 | 73.0k | am.power2 == binary_format<T>::infinite_power()) { |
5739 | 5.37k | answer.ec = tinyobj_ff::ff_errc::result_out_of_range; |
5740 | 5.37k | } |
5741 | 74.6k | return answer; |
5742 | 1.73M | } fast_float::from_chars_result_t<char> fast_float::from_chars_advanced<double, char>(fast_float::parsed_number_string_t<char>&, double&) Line | Count | Source | 5658 | 1.73M | from_chars_advanced(parsed_number_string_t<UC> &pns, T &value) noexcept { | 5659 | | | 5660 | 1.73M | static_assert(is_supported_float_type<T>::value, | 5661 | 1.73M | "only some floating-point types are supported"); | 5662 | 1.73M | static_assert(is_supported_char_type<UC>::value, | 5663 | 1.73M | "only char, wchar_t, char16_t and char32_t are supported"); | 5664 | | | 5665 | 1.73M | from_chars_result_t<UC> answer; | 5666 | | | 5667 | 1.73M | answer.ec = tinyobj_ff::ff_errc(); // be optimistic | 5668 | 1.73M | answer.ptr = pns.lastmatch; | 5669 | | // The implementation of the Clinger's fast path is convoluted because | 5670 | | // we want round-to-nearest in all cases, irrespective of the rounding mode | 5671 | | // selected on the thread. | 5672 | | // We proceed optimistically, assuming that detail::rounds_to_nearest() | 5673 | | // returns true. | 5674 | 1.73M | if (binary_format<T>::min_exponent_fast_path() <= pns.exponent && | 5675 | 1.71M | pns.exponent <= binary_format<T>::max_exponent_fast_path() && | 5676 | 1.70M | !pns.too_many_digits) { | 5677 | | // Unfortunately, the conventional Clinger's fast path is only possible | 5678 | | // when the system rounds to the nearest float. | 5679 | | // | 5680 | | // We expect the next branch to almost always be selected. | 5681 | | // We could check it first (before the previous branch), but | 5682 | | // there might be performance advantages at having the check | 5683 | | // be last. | 5684 | 1.66M | if (!cpp20_and_in_constexpr() && detail::rounds_to_nearest()) { | 5685 | | // We have that fegetround() == FE_TONEAREST. | 5686 | | // Next is Clinger's fast path. | 5687 | 1.66M | if (pns.mantissa <= binary_format<T>::max_mantissa_fast_path()) { | 5688 | 1.65M | value = T(pns.mantissa); | 5689 | 1.65M | if (pns.exponent < 0) { | 5690 | 29.0k | value = value / binary_format<T>::exact_power_of_ten(-pns.exponent); | 5691 | 1.63M | } else { | 5692 | 1.63M | value = value * binary_format<T>::exact_power_of_ten(pns.exponent); | 5693 | 1.63M | } | 5694 | 1.65M | if (pns.negative) { | 5695 | 4.18k | value = -value; | 5696 | 4.18k | } | 5697 | 1.65M | return answer; | 5698 | 1.65M | } | 5699 | 1.66M | } else { | 5700 | | // We do not have that fegetround() == FE_TONEAREST. | 5701 | | // Next is a modified Clinger's fast path, inspired by Jakub JelÃnek's | 5702 | | // proposal | 5703 | 0 | if (pns.exponent >= 0 && | 5704 | 0 | pns.mantissa <= | 5705 | 0 | binary_format<T>::max_mantissa_fast_path(pns.exponent)) { | 5706 | 0 | #if defined(__clang__) || defined(FASTFLOAT_32BIT) | 5707 | | // Clang may map 0 to -0.0 when fegetround() == FE_DOWNWARD | 5708 | 0 | if (pns.mantissa == 0) { | 5709 | 0 | value = pns.negative ? T(-0.) : T(0.); | 5710 | 0 | return answer; | 5711 | 0 | } | 5712 | 0 | #endif | 5713 | 0 | value = T(pns.mantissa) * | 5714 | 0 | binary_format<T>::exact_power_of_ten(pns.exponent); | 5715 | 0 | if (pns.negative) { | 5716 | 0 | value = -value; | 5717 | 0 | } | 5718 | 0 | return answer; | 5719 | 0 | } | 5720 | 0 | } | 5721 | 1.66M | } | 5722 | 74.6k | adjusted_mantissa am = | 5723 | 74.6k | compute_float<binary_format<T>>(pns.exponent, pns.mantissa); | 5724 | 74.6k | if (pns.too_many_digits && am.power2 >= 0) { | 5725 | 68.9k | if (am != compute_float<binary_format<T>>(pns.exponent, pns.mantissa + 1)) { | 5726 | 35.1k | am = compute_error<binary_format<T>>(pns.exponent, pns.mantissa); | 5727 | 35.1k | } | 5728 | 68.9k | } | 5729 | | // If we called compute_float<binary_format<T>>(pns.exponent, pns.mantissa) | 5730 | | // and we have an invalid power (am.power2 < 0), then we need to go the long | 5731 | | // way around again. This is very uncommon. | 5732 | 74.6k | if (am.power2 < 0) { | 5733 | 35.1k | am = digit_comp<T>(pns, am); | 5734 | 35.1k | } | 5735 | 74.6k | to_float(pns.negative, am, value); | 5736 | | // Test for over/underflow. | 5737 | 74.6k | if ((pns.mantissa != 0 && am.mantissa == 0 && am.power2 == 0) || | 5738 | 73.0k | am.power2 == binary_format<T>::infinite_power()) { | 5739 | 5.37k | answer.ec = tinyobj_ff::ff_errc::result_out_of_range; | 5740 | 5.37k | } | 5741 | 74.6k | return answer; | 5742 | 1.73M | } |
Unexecuted instantiation: fast_float::from_chars_result_t<char> fast_float::from_chars_advanced<float, char>(fast_float::parsed_number_string_t<char>&, float&) |
5743 | | |
5744 | | template <typename T, typename UC> |
5745 | | FASTFLOAT_CONSTEXPR20 from_chars_result_t<UC> |
5746 | | from_chars_float_advanced(UC const *first, UC const *last, T &value, |
5747 | 1.87M | parse_options_t<UC> options) noexcept { |
5748 | | |
5749 | 1.87M | static_assert(is_supported_float_type<T>::value, |
5750 | 1.87M | "only some floating-point types are supported"); |
5751 | 1.87M | static_assert(is_supported_char_type<UC>::value, |
5752 | 1.87M | "only char, wchar_t, char16_t and char32_t are supported"); |
5753 | | |
5754 | 1.87M | chars_format const fmt = detail::adjust_for_feature_macros(options.format); |
5755 | | |
5756 | 1.87M | from_chars_result_t<UC> answer; |
5757 | 1.87M | if (uint64_t(fmt & chars_format::skip_white_space)) { |
5758 | 0 | while ((first != last) && fast_float::is_space(*first)) { |
5759 | 0 | first++; |
5760 | 0 | } |
5761 | 0 | } |
5762 | 1.87M | if (first == last) { |
5763 | 0 | answer.ec = tinyobj_ff::ff_errc::invalid_argument; |
5764 | 0 | answer.ptr = first; |
5765 | 0 | return answer; |
5766 | 0 | } |
5767 | 1.87M | parsed_number_string_t<UC> pns = |
5768 | 1.87M | uint64_t(fmt & detail::basic_json_fmt) |
5769 | 1.87M | ? parse_number_string<true, UC>(first, last, options) |
5770 | 1.87M | : parse_number_string<false, UC>(first, last, options); |
5771 | 1.87M | if (!pns.valid) { |
5772 | 142k | if (uint64_t(fmt & chars_format::no_infnan)) { |
5773 | 0 | answer.ec = tinyobj_ff::ff_errc::invalid_argument; |
5774 | 0 | answer.ptr = first; |
5775 | 0 | return answer; |
5776 | 142k | } else { |
5777 | 142k | return detail::parse_infnan(first, last, value, fmt); |
5778 | 142k | } |
5779 | 142k | } |
5780 | | |
5781 | | // call overload that takes parsed_number_string_t directly. |
5782 | 1.73M | return from_chars_advanced(pns, value); |
5783 | 1.87M | } fast_float::from_chars_result_t<char> fast_float::from_chars_float_advanced<double, char>(char const*, char const*, double&, fast_float::parse_options_t<char>) Line | Count | Source | 5747 | 1.87M | parse_options_t<UC> options) noexcept { | 5748 | | | 5749 | 1.87M | static_assert(is_supported_float_type<T>::value, | 5750 | 1.87M | "only some floating-point types are supported"); | 5751 | 1.87M | static_assert(is_supported_char_type<UC>::value, | 5752 | 1.87M | "only char, wchar_t, char16_t and char32_t are supported"); | 5753 | | | 5754 | 1.87M | chars_format const fmt = detail::adjust_for_feature_macros(options.format); | 5755 | | | 5756 | 1.87M | from_chars_result_t<UC> answer; | 5757 | 1.87M | if (uint64_t(fmt & chars_format::skip_white_space)) { | 5758 | 0 | while ((first != last) && fast_float::is_space(*first)) { | 5759 | 0 | first++; | 5760 | 0 | } | 5761 | 0 | } | 5762 | 1.87M | if (first == last) { | 5763 | 0 | answer.ec = tinyobj_ff::ff_errc::invalid_argument; | 5764 | 0 | answer.ptr = first; | 5765 | 0 | return answer; | 5766 | 0 | } | 5767 | 1.87M | parsed_number_string_t<UC> pns = | 5768 | 1.87M | uint64_t(fmt & detail::basic_json_fmt) | 5769 | 1.87M | ? parse_number_string<true, UC>(first, last, options) | 5770 | 1.87M | : parse_number_string<false, UC>(first, last, options); | 5771 | 1.87M | if (!pns.valid) { | 5772 | 142k | if (uint64_t(fmt & chars_format::no_infnan)) { | 5773 | 0 | answer.ec = tinyobj_ff::ff_errc::invalid_argument; | 5774 | 0 | answer.ptr = first; | 5775 | 0 | return answer; | 5776 | 142k | } else { | 5777 | 142k | return detail::parse_infnan(first, last, value, fmt); | 5778 | 142k | } | 5779 | 142k | } | 5780 | | | 5781 | | // call overload that takes parsed_number_string_t directly. | 5782 | 1.73M | return from_chars_advanced(pns, value); | 5783 | 1.87M | } |
Unexecuted instantiation: fast_float::from_chars_result_t<char> fast_float::from_chars_float_advanced<float, char>(char const*, char const*, float&, fast_float::parse_options_t<char>) |
5784 | | |
5785 | | template <typename T, typename UC, typename> |
5786 | | FASTFLOAT_CONSTEXPR20 from_chars_result_t<UC> |
5787 | | from_chars(UC const *first, UC const *last, T &value, int base) noexcept { |
5788 | | |
5789 | | static_assert(is_supported_integer_type<T>::value, |
5790 | | "only integer types are supported"); |
5791 | | static_assert(is_supported_char_type<UC>::value, |
5792 | | "only char, wchar_t, char16_t and char32_t are supported"); |
5793 | | |
5794 | | parse_options_t<UC> options; |
5795 | | options.base = base; |
5796 | | return from_chars_advanced(first, last, value, options); |
5797 | | } |
5798 | | |
5799 | | template <typename T, typename UC> |
5800 | | FASTFLOAT_CONSTEXPR20 from_chars_result_t<UC> |
5801 | | from_chars_int_advanced(UC const *first, UC const *last, T &value, |
5802 | | parse_options_t<UC> options) noexcept { |
5803 | | |
5804 | | static_assert(is_supported_integer_type<T>::value, |
5805 | | "only integer types are supported"); |
5806 | | static_assert(is_supported_char_type<UC>::value, |
5807 | | "only char, wchar_t, char16_t and char32_t are supported"); |
5808 | | |
5809 | | chars_format const fmt = detail::adjust_for_feature_macros(options.format); |
5810 | | int const base = options.base; |
5811 | | |
5812 | | from_chars_result_t<UC> answer; |
5813 | | if (uint64_t(fmt & chars_format::skip_white_space)) { |
5814 | | while ((first != last) && fast_float::is_space(*first)) { |
5815 | | first++; |
5816 | | } |
5817 | | } |
5818 | | if (first == last || base < 2 || base > 36) { |
5819 | | answer.ec = tinyobj_ff::ff_errc::invalid_argument; |
5820 | | answer.ptr = first; |
5821 | | return answer; |
5822 | | } |
5823 | | |
5824 | | return parse_int_string(first, last, value, options); |
5825 | | } |
5826 | | |
5827 | | template <size_t TypeIx> struct from_chars_advanced_caller { |
5828 | | static_assert(TypeIx > 0, "unsupported type"); |
5829 | | }; |
5830 | | |
5831 | | template <> struct from_chars_advanced_caller<1> { |
5832 | | template <typename T, typename UC> |
5833 | | FASTFLOAT_CONSTEXPR20 static from_chars_result_t<UC> |
5834 | | call(UC const *first, UC const *last, T &value, |
5835 | 1.87M | parse_options_t<UC> options) noexcept { |
5836 | 1.87M | return from_chars_float_advanced(first, last, value, options); |
5837 | 1.87M | } fast_float::from_chars_result_t<char> fast_float::from_chars_advanced_caller<1ul>::call<double, char>(char const*, char const*, double&, fast_float::parse_options_t<char>) Line | Count | Source | 5835 | 1.87M | parse_options_t<UC> options) noexcept { | 5836 | 1.87M | return from_chars_float_advanced(first, last, value, options); | 5837 | 1.87M | } |
Unexecuted instantiation: fast_float::from_chars_result_t<char> fast_float::from_chars_advanced_caller<1ul>::call<float, char>(char const*, char const*, float&, fast_float::parse_options_t<char>) |
5838 | | }; |
5839 | | |
5840 | | template <> struct from_chars_advanced_caller<2> { |
5841 | | template <typename T, typename UC> |
5842 | | FASTFLOAT_CONSTEXPR20 static from_chars_result_t<UC> |
5843 | | call(UC const *first, UC const *last, T &value, |
5844 | | parse_options_t<UC> options) noexcept { |
5845 | | return from_chars_int_advanced(first, last, value, options); |
5846 | | } |
5847 | | }; |
5848 | | |
5849 | | template <typename T, typename UC> |
5850 | | FASTFLOAT_CONSTEXPR20 from_chars_result_t<UC> |
5851 | | from_chars_advanced(UC const *first, UC const *last, T &value, |
5852 | 1.87M | parse_options_t<UC> options) noexcept { |
5853 | 1.87M | return from_chars_advanced_caller< |
5854 | 1.87M | size_t(is_supported_float_type<T>::value) + |
5855 | 1.87M | 2 * size_t(is_supported_integer_type<T>::value)>::call(first, last, value, |
5856 | 1.87M | options); |
5857 | 1.87M | } fast_float::from_chars_result_t<char> fast_float::from_chars_advanced<double, char>(char const*, char const*, double&, fast_float::parse_options_t<char>) Line | Count | Source | 5852 | 1.87M | parse_options_t<UC> options) noexcept { | 5853 | 1.87M | return from_chars_advanced_caller< | 5854 | 1.87M | size_t(is_supported_float_type<T>::value) + | 5855 | 1.87M | 2 * size_t(is_supported_integer_type<T>::value)>::call(first, last, value, | 5856 | 1.87M | options); | 5857 | 1.87M | } |
Unexecuted instantiation: fast_float::from_chars_result_t<char> fast_float::from_chars_advanced<float, char>(char const*, char const*, float&, fast_float::parse_options_t<char>) |
5858 | | |
5859 | | } // namespace fast_float |
5860 | | |
5861 | | #endif |
5862 | | |
5863 | | |
5864 | | // --- End embedded fast_float --- |
5865 | | |
5866 | | // Clean up fast_float macros to avoid polluting the user's namespace. |
5867 | | #undef FASTFLOAT_32BIT |
5868 | | #undef FASTFLOAT_32BIT_LIMB |
5869 | | #undef FASTFLOAT_64BIT |
5870 | | #undef FASTFLOAT_64BIT_LIMB |
5871 | | #undef FASTFLOAT_ASCII_NUMBER_H |
5872 | | #undef FASTFLOAT_ASSERT |
5873 | | #undef FASTFLOAT_BIGINT_H |
5874 | | #undef FASTFLOAT_CONSTEXPR14 |
5875 | | #undef FASTFLOAT_CONSTEXPR20 |
5876 | | #undef FASTFLOAT_CONSTEXPR_FEATURE_DETECT_H |
5877 | | #undef FASTFLOAT_DEBUG_ASSERT |
5878 | | #undef FASTFLOAT_DECIMAL_TO_BINARY_H |
5879 | | #undef FASTFLOAT_DETAIL_MUST_DEFINE_CONSTEXPR_VARIABLE |
5880 | | #undef FASTFLOAT_DIGIT_COMPARISON_H |
5881 | | #undef FASTFLOAT_ENABLE_IF |
5882 | | #undef FASTFLOAT_FAST_FLOAT_H |
5883 | | #undef FASTFLOAT_FAST_TABLE_H |
5884 | | #undef FASTFLOAT_FLOAT_COMMON_H |
5885 | | #undef FASTFLOAT_HAS_BIT_CAST |
5886 | | #undef FASTFLOAT_HAS_IS_CONSTANT_EVALUATED |
5887 | | #undef FASTFLOAT_HAS_SIMD |
5888 | | #undef FASTFLOAT_IF_CONSTEXPR17 |
5889 | | #undef FASTFLOAT_IS_BIG_ENDIAN |
5890 | | #undef FASTFLOAT_IS_CONSTEXPR |
5891 | | #undef FASTFLOAT_NEON |
5892 | | #undef FASTFLOAT_PARSE_NUMBER_H |
5893 | | #undef fastfloat_really_inline |
5894 | | #undef FASTFLOAT_SIMD_DISABLE_WARNINGS |
5895 | | #undef FASTFLOAT_SIMD_RESTORE_WARNINGS |
5896 | | #undef FASTFLOAT_SSE2 |
5897 | | #undef FASTFLOAT_STRINGIZE |
5898 | | #undef FASTFLOAT_STRINGIZE_IMPL |
5899 | | #undef FASTFLOAT_TRY |
5900 | | #undef FASTFLOAT_VERSION |
5901 | | #undef FASTFLOAT_VERSION_MAJOR |
5902 | | #undef FASTFLOAT_VERSION_MINOR |
5903 | | #undef FASTFLOAT_VERSION_PATCH |
5904 | | #undef FASTFLOAT_VERSION_STR |
5905 | | #undef FASTFLOAT_VISUAL_STUDIO |
5906 | | |
5907 | | #endif // TINYOBJLOADER_DISABLE_FAST_FLOAT |
5908 | | |
5909 | | namespace tinyobj { |
5910 | | |
5911 | 11.4k | MaterialReader::~MaterialReader() {} |
5912 | | |
5913 | | // Byte-stream reader for bounds-checked text parsing. |
5914 | | // Replaces raw `const char*` token pointers with `(buf, len, idx)` triple. |
5915 | | // Every byte access is guarded by an EOF check. |
5916 | | class StreamReader { |
5917 | | public: |
5918 | | // Maximum number of bytes StreamReader will buffer from std::istream. |
5919 | | // Define this macro to a larger value if your application needs to parse |
5920 | | // very large streamed OBJ/MTL content. |
5921 | | #ifndef TINYOBJLOADER_STREAM_READER_MAX_BYTES |
5922 | 23.3k | #define TINYOBJLOADER_STREAM_READER_MAX_BYTES (size_t(256) * size_t(1024) * size_t(1024)) |
5923 | | #endif |
5924 | | |
5925 | | StreamReader(const char *buf, size_t length) |
5926 | 0 | : buf_(buf), length_(length), idx_(0), line_num_(1), col_num_(1) {} |
5927 | | |
5928 | | // Non-copyable, non-movable: buf_ may point into owned_buf_. |
5929 | | StreamReader(const StreamReader &) /* = delete */; |
5930 | | StreamReader &operator=(const StreamReader &) /* = delete */; |
5931 | | |
5932 | | // Build from std::istream by reading all content into an internal buffer. |
5933 | 23.3k | explicit StreamReader(std::istream &is) : buf_(NULL), length_(0), idx_(0), line_num_(1), col_num_(1) { |
5934 | 23.3k | const size_t max_stream_bytes = TINYOBJLOADER_STREAM_READER_MAX_BYTES; |
5935 | 23.3k | std::streampos start_pos = is.tellg(); |
5936 | 23.3k | bool can_seek = (start_pos != std::streampos(-1)); |
5937 | 23.3k | if (can_seek) { |
5938 | 23.3k | is.seekg(0, std::ios::end); |
5939 | 23.3k | std::streampos end_pos = is.tellg(); |
5940 | 23.3k | if (end_pos >= start_pos) { |
5941 | 23.3k | std::streamoff remaining_off = static_cast<std::streamoff>(end_pos - start_pos); |
5942 | 23.3k | is.seekg(start_pos); |
5943 | 23.3k | unsigned long long remaining_ull = static_cast<unsigned long long>(remaining_off); |
5944 | 23.3k | if (remaining_ull > static_cast<unsigned long long>((std::numeric_limits<size_t>::max)())) { |
5945 | 0 | std::stringstream ss; |
5946 | 0 | ss << "input stream too large for this platform (" << remaining_ull |
5947 | 0 | << " bytes exceeds size_t max " << (std::numeric_limits<size_t>::max)() << ")\n"; |
5948 | 0 | push_error(ss.str()); |
5949 | 0 | buf_ = ""; |
5950 | 0 | length_ = 0; |
5951 | 0 | return; |
5952 | 0 | } |
5953 | 23.3k | size_t remaining_size = static_cast<size_t>(remaining_ull); |
5954 | 23.3k | if (remaining_size > max_stream_bytes) { |
5955 | 0 | std::stringstream ss; |
5956 | 0 | ss << "input stream too large (" << remaining_size |
5957 | 0 | << " bytes exceeds limit " << max_stream_bytes << " bytes)\n"; |
5958 | 0 | push_error(ss.str()); |
5959 | 0 | buf_ = ""; |
5960 | 0 | length_ = 0; |
5961 | 0 | return; |
5962 | 0 | } |
5963 | 23.3k | owned_buf_.resize(remaining_size); |
5964 | 23.3k | if (remaining_size > 0) { |
5965 | 16.7k | is.read(&owned_buf_[0], static_cast<std::streamsize>(remaining_size)); |
5966 | 16.7k | } |
5967 | 23.3k | size_t actually_read = static_cast<size_t>(is.gcount()); |
5968 | 23.3k | owned_buf_.resize(actually_read); |
5969 | 23.3k | } |
5970 | 23.3k | } |
5971 | 23.3k | if (!can_seek || owned_buf_.empty()) { |
5972 | | // Stream doesn't support seeking, or seek probing failed. |
5973 | 13 | if (can_seek) is.seekg(start_pos); |
5974 | 13 | is.clear(); |
5975 | 13 | std::vector<char> content; |
5976 | 13 | char chunk[4096]; |
5977 | 13 | size_t total_read = 0; |
5978 | 13 | while (is.good()) { |
5979 | 13 | is.read(chunk, static_cast<std::streamsize>(sizeof(chunk))); |
5980 | 13 | std::streamsize nread = is.gcount(); |
5981 | 13 | if (nread <= 0) break; |
5982 | 0 | size_t n = static_cast<size_t>(nread); |
5983 | 0 | if (n > (max_stream_bytes - total_read)) { |
5984 | 0 | std::stringstream ss; |
5985 | 0 | ss << "input stream too large (exceeds limit " << max_stream_bytes |
5986 | 0 | << " bytes)\n"; |
5987 | 0 | push_error(ss.str()); |
5988 | 0 | owned_buf_.clear(); |
5989 | 0 | buf_ = ""; |
5990 | 0 | length_ = 0; |
5991 | 0 | return; |
5992 | 0 | } |
5993 | 0 | content.insert(content.end(), chunk, chunk + n); |
5994 | 0 | total_read += n; |
5995 | 0 | } |
5996 | 13 | owned_buf_.swap(content); |
5997 | 13 | } |
5998 | 23.3k | buf_ = owned_buf_.empty() ? "" : &owned_buf_[0]; |
5999 | 23.3k | length_ = owned_buf_.size(); |
6000 | 23.3k | } |
6001 | | |
6002 | 49.3M | bool eof() const { return idx_ >= length_; } |
6003 | 0 | size_t tell() const { return idx_; } |
6004 | 0 | size_t size() const { return length_; } |
6005 | 8.60M | size_t line_num() const { return line_num_; } |
6006 | 0 | size_t col_num() const { return col_num_; } |
6007 | | |
6008 | 123M | char peek() const { |
6009 | 123M | if (idx_ >= length_) return '\0'; |
6010 | 123M | return buf_[idx_]; |
6011 | 123M | } |
6012 | | |
6013 | 0 | char get() { |
6014 | 0 | if (idx_ >= length_) return '\0'; |
6015 | 0 | char c = buf_[idx_++]; |
6016 | 0 | if (c == '\n') { line_num_++; col_num_ = 1; } else { col_num_++; } |
6017 | 0 | return c; |
6018 | 0 | } |
6019 | | |
6020 | 53.4M | void advance(size_t n) { |
6021 | 131M | for (size_t i = 0; i < n && idx_ < length_; i++) { |
6022 | 77.7M | if (buf_[idx_] == '\n') { line_num_++; col_num_ = 1; } else { col_num_++; } |
6023 | 77.7M | idx_++; |
6024 | 77.7M | } |
6025 | 53.4M | } |
6026 | | |
6027 | 52.6M | void skip_space() { |
6028 | 54.8M | while (idx_ < length_ && (buf_[idx_] == ' ' || buf_[idx_] == '\t')) { |
6029 | 2.22M | col_num_++; |
6030 | 2.22M | idx_++; |
6031 | 2.22M | } |
6032 | 52.6M | } |
6033 | | |
6034 | 6.16M | void skip_space_and_cr() { |
6035 | 12.1M | while (idx_ < length_ && (buf_[idx_] == ' ' || buf_[idx_] == '\t' || buf_[idx_] == '\r')) { |
6036 | 5.98M | col_num_++; |
6037 | 5.98M | idx_++; |
6038 | 5.98M | } |
6039 | 6.16M | } |
6040 | | |
6041 | 9.60M | void skip_line() { |
6042 | 107M | while (idx_ < length_) { |
6043 | 107M | char c = buf_[idx_]; |
6044 | 107M | if (c == '\n') { |
6045 | 7.13M | idx_++; |
6046 | 7.13M | line_num_++; |
6047 | 7.13M | col_num_ = 1; |
6048 | 7.13M | return; |
6049 | 7.13M | } |
6050 | 99.9M | if (c == '\r') { |
6051 | 2.45M | idx_++; |
6052 | 2.45M | if (idx_ < length_ && buf_[idx_] == '\n') { |
6053 | 184k | idx_++; |
6054 | 184k | } |
6055 | 2.45M | line_num_++; |
6056 | 2.45M | col_num_ = 1; |
6057 | 2.45M | return; |
6058 | 2.45M | } |
6059 | 97.5M | col_num_++; |
6060 | 97.5M | idx_++; |
6061 | 97.5M | } |
6062 | 9.60M | } |
6063 | | |
6064 | 19.7M | bool at_line_end() const { |
6065 | 19.7M | if (idx_ >= length_) return true; |
6066 | 19.7M | char c = buf_[idx_]; |
6067 | 19.7M | return (c == '\n' || c == '\r' || c == '\0'); |
6068 | 19.7M | } |
6069 | | |
6070 | 2.38M | std::string read_line() { |
6071 | 2.38M | std::string result; |
6072 | 120M | while (idx_ < length_) { |
6073 | 120M | char c = buf_[idx_]; |
6074 | 120M | if (c == '\n' || c == '\r') break; |
6075 | 118M | result += c; |
6076 | 118M | col_num_++; |
6077 | 118M | idx_++; |
6078 | 118M | } |
6079 | 2.38M | return result; |
6080 | 2.38M | } |
6081 | | |
6082 | | // Reads a whitespace-delimited token. Used by tests and as a general utility. |
6083 | 0 | std::string read_token() { |
6084 | 0 | skip_space(); |
6085 | 0 | std::string result; |
6086 | 0 | while (idx_ < length_) { |
6087 | 0 | char c = buf_[idx_]; |
6088 | 0 | if (c == ' ' || c == '\t' || c == '\r' || c == '\n' || c == '\0') break; |
6089 | 0 | result += c; |
6090 | 0 | col_num_++; |
6091 | 0 | idx_++; |
6092 | 0 | } |
6093 | 0 | return result; |
6094 | 0 | } |
6095 | | |
6096 | 47.1M | bool match(const char *prefix, size_t len) const { |
6097 | 47.1M | if (idx_ >= length_ || len > length_ - idx_) return false; |
6098 | 47.1M | return (memcmp(buf_ + idx_, prefix, len) == 0); |
6099 | 47.1M | } |
6100 | | |
6101 | 0 | bool char_at(size_t offset, char c) const { |
6102 | 0 | if (idx_ >= length_ || offset >= length_ - idx_) return false; |
6103 | 0 | return buf_[idx_ + offset] == c; |
6104 | 0 | } |
6105 | | |
6106 | 16.1M | char peek_at(size_t offset) const { |
6107 | 16.1M | if (idx_ >= length_ || offset >= length_ - idx_) return '\0'; |
6108 | 16.1M | return buf_[idx_ + offset]; |
6109 | 16.1M | } |
6110 | | |
6111 | 34.1M | const char *current_ptr() const { |
6112 | 34.1M | if (idx_ >= length_) return ""; |
6113 | 33.2M | return buf_ + idx_; |
6114 | 34.1M | } |
6115 | | |
6116 | 35.9M | size_t remaining() const { |
6117 | 35.9M | return (idx_ < length_) ? (length_ - idx_) : 0; |
6118 | 35.9M | } |
6119 | | |
6120 | | // Returns the full text of the current line (for diagnostic display). |
6121 | 1.40k | std::string current_line_text() const { |
6122 | | // Scan backward to find line start |
6123 | 1.40k | size_t line_start = idx_; |
6124 | 3.56M | while (line_start > 0 && buf_[line_start - 1] != '\n' && buf_[line_start - 1] != '\r') { |
6125 | 3.56M | line_start--; |
6126 | 3.56M | } |
6127 | | // Scan forward to find line end |
6128 | 1.40k | size_t line_end = idx_; |
6129 | 16.3M | while (line_end < length_ && buf_[line_end] != '\n' && buf_[line_end] != '\r') { |
6130 | 16.3M | line_end++; |
6131 | 16.3M | } |
6132 | 1.40k | return std::string(buf_ + line_start, line_end - line_start); |
6133 | 1.40k | } |
6134 | | |
6135 | | // Clang-style formatted error with file:line:col and caret. |
6136 | 1.40k | std::string format_error(const std::string &filename, const std::string &msg) const { |
6137 | 1.40k | std::stringstream line_ss, col_ss; |
6138 | 1.40k | line_ss << line_num_; |
6139 | 1.40k | col_ss << col_num_; |
6140 | 1.40k | std::string result; |
6141 | 1.40k | result += filename + ":" + line_ss.str() + ":" + col_ss.str() + ": error: " + msg + "\n"; |
6142 | 1.40k | std::string line_text = current_line_text(); |
6143 | 1.40k | result += line_text + "\n"; |
6144 | | // Build caret line preserving tab alignment |
6145 | 1.40k | std::string caret; |
6146 | 1.40k | size_t caret_pos = (col_num_ > 0) ? (col_num_ - 1) : 0; |
6147 | 3.56M | for (size_t i = 0; i < caret_pos && i < line_text.size(); i++) { |
6148 | 3.56M | caret += (line_text[i] == '\t') ? '\t' : ' '; |
6149 | 3.56M | } |
6150 | 1.40k | caret += "^"; |
6151 | 1.40k | result += caret + "\n"; |
6152 | 1.40k | return result; |
6153 | 1.40k | } |
6154 | | |
6155 | 0 | std::string format_error(const std::string &msg) const { |
6156 | 0 | return format_error("<input>", msg); |
6157 | 0 | } |
6158 | | |
6159 | | // Error stack |
6160 | 0 | void push_error(const std::string &msg) { |
6161 | 0 | errors_.push_back(msg); |
6162 | 0 | } |
6163 | | |
6164 | 0 | void push_formatted_error(const std::string &filename, const std::string &msg) { |
6165 | 0 | errors_.push_back(format_error(filename, msg)); |
6166 | 0 | } |
6167 | | |
6168 | 23.3k | bool has_errors() const { return !errors_.empty(); } |
6169 | | |
6170 | 0 | std::string get_errors() const { |
6171 | 0 | std::string result; |
6172 | 0 | for (size_t i = 0; i < errors_.size(); i++) { |
6173 | 0 | result += errors_[i]; |
6174 | 0 | } |
6175 | 0 | return result; |
6176 | 0 | } |
6177 | | |
6178 | 0 | const std::vector<std::string> &error_stack() const { return errors_; } |
6179 | | |
6180 | 0 | void clear_errors() { errors_.clear(); } |
6181 | | |
6182 | | private: |
6183 | | const char *buf_; |
6184 | | size_t length_; |
6185 | | size_t idx_; |
6186 | | size_t line_num_; |
6187 | | size_t col_num_; |
6188 | | std::vector<char> owned_buf_; |
6189 | | std::vector<std::string> errors_; |
6190 | | }; |
6191 | | |
6192 | | #ifdef TINYOBJLOADER_USE_MMAP |
6193 | | // RAII wrapper for memory-mapped file I/O. |
6194 | | // Opens a file and maps it into memory; the mapping is released on destruction. |
6195 | | // For empty files, data is set to "" and is_mapped remains false so close() |
6196 | | // will not attempt to unmap a string literal. |
6197 | | struct MappedFile { |
6198 | | const char *data; |
6199 | | size_t size; |
6200 | | bool is_mapped; // true when data points to an actual mapped region |
6201 | | #if defined(_WIN32) |
6202 | | HANDLE hFile; |
6203 | | HANDLE hMapping; |
6204 | | #else |
6205 | | void *mapped_ptr; |
6206 | | #endif |
6207 | | |
6208 | | MappedFile() : data(NULL), size(0), is_mapped(false) |
6209 | | #if defined(_WIN32) |
6210 | | , hFile(INVALID_HANDLE_VALUE), hMapping(NULL) |
6211 | | #else |
6212 | | , mapped_ptr(NULL) |
6213 | | #endif |
6214 | | {} |
6215 | | |
6216 | | // Opens and maps the file. Returns true on success. |
6217 | | bool open(const char *filepath) { |
6218 | | #if defined(_WIN32) |
6219 | | std::wstring wfilepath = LongPathW(UTF8ToWchar(std::string(filepath))); |
6220 | | hFile = CreateFileW(wfilepath.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, |
6221 | | OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); |
6222 | | if (hFile == INVALID_HANDLE_VALUE) return false; |
6223 | | LARGE_INTEGER fileSize; |
6224 | | if (!GetFileSizeEx(hFile, &fileSize)) { close(); return false; } |
6225 | | if (fileSize.QuadPart < 0) { close(); return false; } |
6226 | | unsigned long long fsize = static_cast<unsigned long long>(fileSize.QuadPart); |
6227 | | if (fsize > static_cast<unsigned long long>((std::numeric_limits<size_t>::max)())) { |
6228 | | close(); |
6229 | | return false; |
6230 | | } |
6231 | | size = static_cast<size_t>(fsize); |
6232 | | if (size == 0) { data = ""; return true; } // valid but empty; is_mapped stays false |
6233 | | hMapping = CreateFileMappingA(hFile, NULL, PAGE_READONLY, 0, 0, NULL); |
6234 | | if (hMapping == NULL) { close(); return false; } |
6235 | | data = static_cast<const char *>(MapViewOfFile(hMapping, FILE_MAP_READ, 0, 0, 0)); |
6236 | | if (!data) { close(); return false; } |
6237 | | is_mapped = true; |
6238 | | return true; |
6239 | | #else |
6240 | | int fd = ::open(filepath, O_RDONLY); |
6241 | | if (fd == -1) return false; |
6242 | | struct stat sb; |
6243 | | if (fstat(fd, &sb) != 0) { ::close(fd); return false; } |
6244 | | if (sb.st_size < 0) { ::close(fd); return false; } |
6245 | | if (static_cast<unsigned long long>(sb.st_size) > |
6246 | | static_cast<unsigned long long>((std::numeric_limits<size_t>::max)())) { |
6247 | | ::close(fd); |
6248 | | return false; |
6249 | | } |
6250 | | size = static_cast<size_t>(sb.st_size); |
6251 | | if (size == 0) { ::close(fd); data = ""; return true; } // valid but empty |
6252 | | mapped_ptr = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0); |
6253 | | ::close(fd); |
6254 | | if (mapped_ptr == MAP_FAILED) { mapped_ptr = NULL; size = 0; return false; } |
6255 | | data = static_cast<const char *>(mapped_ptr); |
6256 | | is_mapped = true; |
6257 | | return true; |
6258 | | #endif |
6259 | | } |
6260 | | |
6261 | | void close() { |
6262 | | #if defined(_WIN32) |
6263 | | if (is_mapped && data) { UnmapViewOfFile(data); } |
6264 | | data = NULL; |
6265 | | is_mapped = false; |
6266 | | if (hMapping != NULL) { CloseHandle(hMapping); hMapping = NULL; } |
6267 | | if (hFile != INVALID_HANDLE_VALUE) { CloseHandle(hFile); hFile = INVALID_HANDLE_VALUE; } |
6268 | | #else |
6269 | | if (is_mapped && mapped_ptr && mapped_ptr != MAP_FAILED) { munmap(mapped_ptr, size); } |
6270 | | mapped_ptr = NULL; |
6271 | | data = NULL; |
6272 | | is_mapped = false; |
6273 | | #endif |
6274 | | size = 0; |
6275 | | } |
6276 | | |
6277 | | ~MappedFile() { close(); } |
6278 | | |
6279 | | private: |
6280 | | MappedFile(const MappedFile &); // non-copyable |
6281 | | MappedFile &operator=(const MappedFile &); // non-copyable |
6282 | | }; |
6283 | | #endif // TINYOBJLOADER_USE_MMAP |
6284 | | |
6285 | | |
6286 | | struct vertex_index_t { |
6287 | | int v_idx, vt_idx, vn_idx; |
6288 | 5.03M | vertex_index_t() : v_idx(-1), vt_idx(-1), vn_idx(-1) {} |
6289 | 4.99M | explicit vertex_index_t(int idx) : v_idx(idx), vt_idx(idx), vn_idx(idx) {} |
6290 | | vertex_index_t(int vidx, int vtidx, int vnidx) |
6291 | 0 | : v_idx(vidx), vt_idx(vtidx), vn_idx(vnidx) {} |
6292 | | }; |
6293 | | |
6294 | | // Internal data structure for face representation |
6295 | | // index + smoothing group. |
6296 | | struct face_t { |
6297 | | unsigned int |
6298 | | smoothing_group_id; // smoothing group id. 0 = smoothing groupd is off. |
6299 | | int pad_; |
6300 | | std::vector<vertex_index_t> vertex_indices; // face vertex indices. |
6301 | | |
6302 | 529k | face_t() : smoothing_group_id(0), pad_(0) {} |
6303 | | }; |
6304 | | |
6305 | | // Internal data structure for line representation |
6306 | | struct __line_t { |
6307 | | // l v1/vt1 v2/vt2 ... |
6308 | | // In the specification, line primitrive does not have normal index, but |
6309 | | // TinyObjLoader allow it |
6310 | | std::vector<vertex_index_t> vertex_indices; |
6311 | | }; |
6312 | | |
6313 | | // Internal data structure for points representation |
6314 | | struct __points_t { |
6315 | | // p v1 v2 ... |
6316 | | // In the specification, point primitrive does not have normal index and |
6317 | | // texture coord index, but TinyObjLoader allow it. |
6318 | | std::vector<vertex_index_t> vertex_indices; |
6319 | | }; |
6320 | | |
6321 | | struct tag_sizes { |
6322 | 903k | tag_sizes() : num_ints(0), num_reals(0), num_strings(0) {} |
6323 | | int num_ints; |
6324 | | int num_reals; |
6325 | | int num_strings; |
6326 | | }; |
6327 | | |
6328 | | struct obj_shape { |
6329 | | std::vector<real_t> v; |
6330 | | std::vector<real_t> vn; |
6331 | | std::vector<real_t> vt; |
6332 | | }; |
6333 | | |
6334 | | // |
6335 | | // Manages group of primitives(face, line, points, ...) |
6336 | | struct PrimGroup { |
6337 | | std::vector<face_t> faceGroup; |
6338 | | std::vector<__line_t> lineGroup; |
6339 | | std::vector<__points_t> pointsGroup; |
6340 | | |
6341 | 457k | void clear() { |
6342 | 457k | faceGroup.clear(); |
6343 | 457k | lineGroup.clear(); |
6344 | 457k | pointsGroup.clear(); |
6345 | 457k | } |
6346 | | |
6347 | 484k | bool IsEmpty() const { |
6348 | 484k | return faceGroup.empty() && lineGroup.empty() && pointsGroup.empty(); |
6349 | 484k | } |
6350 | | |
6351 | | // TODO(syoyo): bspline, surface, ... |
6352 | | }; |
6353 | | |
6354 | | // See |
6355 | | // http://stackoverflow.com/questions/6089231/getting-std-ifstream-to-handle-lf-cr-and-crlf |
6356 | 3.78M | #define IS_SPACE(x) (((x) == ' ') || ((x) == '\t')) |
6357 | | #define IS_DIGIT(x) \ |
6358 | | (static_cast<unsigned int>((x) - '0') < static_cast<unsigned int>(10)) |
6359 | 805k | #define IS_NEW_LINE(x) (((x) == '\r') || ((x) == '\n') || ((x) == '\0')) |
6360 | | |
6361 | | template <typename T> |
6362 | 18.0k | static inline std::string toString(const T &t) { |
6363 | 18.0k | std::stringstream ss; |
6364 | 18.0k | ss << t; |
6365 | 18.0k | return ss.str(); |
6366 | 18.0k | } |
6367 | | |
6368 | 0 | static inline std::string removeUtf8Bom(const std::string& input) { |
6369 | 0 | // UTF-8 BOM = 0xEF,0xBB,0xBF |
6370 | 0 | if (input.size() >= 3 && |
6371 | 0 | static_cast<unsigned char>(input[0]) == 0xEF && |
6372 | 0 | static_cast<unsigned char>(input[1]) == 0xBB && |
6373 | 0 | static_cast<unsigned char>(input[2]) == 0xBF) { |
6374 | 0 | return input.substr(3); // Skip BOM |
6375 | 0 | } |
6376 | 0 | return input; |
6377 | 0 | } |
6378 | | |
6379 | | // Trim trailing spaces and tabs from a string. |
6380 | 2.02M | static inline std::string trimTrailingWhitespace(const std::string &s) { |
6381 | 2.02M | size_t end = s.find_last_not_of(" \t"); |
6382 | 2.02M | if (end == std::string::npos) return ""; |
6383 | 2.01M | return s.substr(0, end + 1); |
6384 | 2.02M | } |
6385 | | |
6386 | | struct warning_context { |
6387 | | std::string *warn; |
6388 | | size_t line_number; |
6389 | | std::string filename; |
6390 | | }; |
6391 | | |
6392 | | // Safely convert size_t to int, clamping at INT_MAX to prevent overflow. |
6393 | 14.9M | static inline int size_to_int(size_t sz) { |
6394 | 14.9M | return sz > static_cast<size_t>(INT_MAX) ? INT_MAX : static_cast<int>(sz); |
6395 | 14.9M | } |
6396 | | |
6397 | | // Make index zero-base, and also support relative index. |
6398 | | static inline bool fixIndex(int idx, int n, int *ret, bool allow_zero, |
6399 | 4.99M | const warning_context &context) { |
6400 | 4.99M | if (!ret) { |
6401 | 0 | return false; |
6402 | 0 | } |
6403 | | |
6404 | 4.99M | if (idx > 0) { |
6405 | 4.97M | (*ret) = idx - 1; |
6406 | 4.97M | return true; |
6407 | 4.97M | } |
6408 | | |
6409 | 18.7k | if (idx == 0) { |
6410 | | // zero is not allowed according to the spec. |
6411 | 18.0k | if (context.warn) { |
6412 | 18.0k | (*context.warn) += |
6413 | 18.0k | context.filename + ":" + toString(context.line_number) + |
6414 | 18.0k | ": warning: zero value index found (will have a value of -1 for " |
6415 | 18.0k | "normal and tex indices)\n"; |
6416 | 18.0k | } |
6417 | | |
6418 | 18.0k | (*ret) = idx - 1; |
6419 | 18.0k | return allow_zero; |
6420 | 18.0k | } |
6421 | | |
6422 | 618 | if (idx < 0) { |
6423 | 618 | (*ret) = n + idx; // negative value = relative |
6424 | 618 | if ((*ret) < 0) { |
6425 | 46 | return false; // invalid relative index |
6426 | 46 | } |
6427 | 572 | return true; |
6428 | 618 | } |
6429 | | |
6430 | 0 | return false; // never reach here. |
6431 | 618 | } |
6432 | | |
6433 | 4.00k | static inline std::string parseString(const char **token) { |
6434 | 4.00k | std::string s; |
6435 | 4.00k | (*token) += strspn((*token), " \t"); |
6436 | 4.00k | size_t e = strcspn((*token), " \t\r"); |
6437 | 4.00k | s = std::string((*token), &(*token)[e]); |
6438 | 4.00k | (*token) += e; |
6439 | 4.00k | return s; |
6440 | 4.00k | } |
6441 | | |
6442 | 1.83k | static inline int parseInt(const char **token) { |
6443 | 1.83k | (*token) += strspn((*token), " \t"); |
6444 | 1.83k | int i = atoi((*token)); |
6445 | 1.83k | (*token) += strcspn((*token), " \t\r"); |
6446 | 1.83k | return i; |
6447 | 1.83k | } |
6448 | | |
6449 | | #ifndef TINYOBJLOADER_DISABLE_FAST_FLOAT |
6450 | | |
6451 | | // ---- fast_float-based float parser (bit-exact with strtod, ~3x faster) ---- |
6452 | | |
6453 | | namespace detail_fp { |
6454 | | |
6455 | | // Case-insensitive prefix match. Returns pointer past matched prefix, or NULL. |
6456 | | static inline const char *match_iprefix(const char *p, const char *end, |
6457 | 134k | const char *prefix) { |
6458 | 399k | while (*prefix) { |
6459 | 369k | if (p == end) return NULL; |
6460 | 364k | char c = *p; |
6461 | 364k | char e = *prefix; |
6462 | 364k | if (c >= 'A' && c <= 'Z') c += 32; |
6463 | 364k | if (e >= 'A' && e <= 'Z') e += 32; |
6464 | 364k | if (c != e) return NULL; |
6465 | 264k | ++p; |
6466 | 264k | ++prefix; |
6467 | 264k | } |
6468 | 29.7k | return p; |
6469 | 134k | } |
6470 | | |
6471 | | // Try to parse nan/inf. Returns true if matched, sets *result and *end_ptr. |
6472 | | static inline bool tryParseNanInf(const char *first, const char *last, |
6473 | 49.2k | double *result, const char **end_ptr) { |
6474 | 49.2k | if (first >= last) return false; |
6475 | | |
6476 | 49.2k | const char *p = first; |
6477 | 49.2k | bool negative = false; |
6478 | | |
6479 | 49.2k | if (*p == '-') { |
6480 | 22.0k | negative = true; |
6481 | 22.0k | ++p; |
6482 | 27.2k | } else if (*p == '+') { |
6483 | 5.33k | ++p; |
6484 | 5.33k | } |
6485 | | |
6486 | 49.2k | if (p >= last) return false; |
6487 | | |
6488 | | // Try "nan" |
6489 | 49.2k | const char *after = match_iprefix(p, last, "nan"); |
6490 | 49.2k | if (after) { |
6491 | 3.04k | *result = 0.0; // nan -> 0.0 for OBJ |
6492 | 3.04k | *end_ptr = after; |
6493 | 3.04k | return true; |
6494 | 3.04k | } |
6495 | | |
6496 | | // Try "infinity" first (longer match), then "inf" |
6497 | 46.2k | after = match_iprefix(p, last, "infinity"); |
6498 | 46.2k | if (after) { |
6499 | 6.78k | *result = negative ? std::numeric_limits<double>::lowest() |
6500 | 6.78k | : (std::numeric_limits<double>::max)(); |
6501 | 6.78k | *end_ptr = after; |
6502 | 6.78k | return true; |
6503 | 6.78k | } |
6504 | | |
6505 | 39.4k | after = match_iprefix(p, last, "inf"); |
6506 | 39.4k | if (after) { |
6507 | 19.9k | *result = negative ? std::numeric_limits<double>::lowest() |
6508 | 19.9k | : (std::numeric_limits<double>::max)(); |
6509 | 19.9k | *end_ptr = after; |
6510 | 19.9k | return true; |
6511 | 19.9k | } |
6512 | | |
6513 | 19.5k | return false; |
6514 | 39.4k | } |
6515 | | |
6516 | | } // namespace detail_fp |
6517 | | |
6518 | | // Tries to parse a floating point number located at s. |
6519 | | // Uses fast_float::from_chars for bit-exact, high-performance parsing. |
6520 | | // Handles OBJ quirks: leading '+', nan/inf with replacement values. |
6521 | | // |
6522 | | // s_end should be a location in the string where reading should absolutely |
6523 | | // stop. For example at the end of the string, to prevent buffer overflows. |
6524 | | // |
6525 | | // If the parsing is a success, result is set to the parsed value and true |
6526 | | // is returned. |
6527 | | // |
6528 | 1.94M | static bool tryParseDouble(const char *s, const char *s_end, double *result) { |
6529 | 1.94M | if (!s || !s_end || !result || s >= s_end) { |
6530 | 37.5k | return false; |
6531 | 37.5k | } |
6532 | | |
6533 | | // Check for nan/inf (starts with [nNiI] or [+-] followed by [nNiI]) |
6534 | 1.90M | const char *p = s; |
6535 | 1.90M | if (p < s_end && (*p == '+' || *p == '-')) ++p; |
6536 | 1.90M | if (p < s_end) { |
6537 | 1.89M | char fc = *p; |
6538 | 1.89M | if (fc >= 'A' && fc <= 'Z') fc += 32; |
6539 | 1.89M | if (fc == 'n' || fc == 'i') { |
6540 | 49.2k | const char *end_ptr; |
6541 | 49.2k | if (detail_fp::tryParseNanInf(s, s_end, result, &end_ptr)) { |
6542 | 29.7k | return true; |
6543 | 29.7k | } |
6544 | 49.2k | } |
6545 | 1.89M | } |
6546 | | |
6547 | | // Use allow_leading_plus so fast_float handles '+' natively. |
6548 | 1.87M | double tmp; |
6549 | 1.87M | auto r = fast_float::from_chars(s, s_end, tmp, |
6550 | 1.87M | fast_float::chars_format::general | |
6551 | 1.87M | fast_float::chars_format::allow_leading_plus); |
6552 | 1.87M | if (r.ec == tinyobj_ff::ff_errc::ok) { |
6553 | 1.72M | *result = tmp; |
6554 | 1.72M | return true; |
6555 | 1.72M | } |
6556 | | // On error (invalid_argument, result_out_of_range), *result is unchanged. |
6557 | | |
6558 | 147k | return false; |
6559 | 1.87M | } |
6560 | | |
6561 | 172k | static inline real_t parseReal(const char **token, double default_value = 0.0) { |
6562 | 172k | (*token) += strspn((*token), " \t"); |
6563 | 172k | const char *end = (*token) + strcspn((*token), " \t\r"); |
6564 | 172k | double val = default_value; |
6565 | 172k | tryParseDouble((*token), end, &val); |
6566 | 172k | real_t f = static_cast<real_t>(val); |
6567 | 172k | (*token) = end; |
6568 | 172k | return f; |
6569 | 172k | } |
6570 | | |
6571 | 0 | static inline bool parseReal(const char **token, real_t *out) { |
6572 | 0 | (*token) += strspn((*token), " \t"); |
6573 | 0 | const char *end = (*token) + strcspn((*token), " \t\r"); |
6574 | 0 | double val; |
6575 | 0 | bool ret = tryParseDouble((*token), end, &val); |
6576 | 0 | if (ret) { |
6577 | 0 | real_t f = static_cast<real_t>(val); |
6578 | 0 | (*out) = f; |
6579 | 0 | } |
6580 | 0 | (*token) = end; |
6581 | 0 | return ret; |
6582 | 0 | } |
6583 | | |
6584 | | #else // TINYOBJLOADER_DISABLE_FAST_FLOAT |
6585 | | |
6586 | | // ---- Legacy hand-written float parser (fallback) ---- |
6587 | | |
6588 | | // Tries to parse a floating point number located at s. |
6589 | | // |
6590 | | // s_end should be a location in the string where reading should absolutely |
6591 | | // stop. For example at the end of the string, to prevent buffer overflows. |
6592 | | // |
6593 | | // Parses the following EBNF grammar: |
6594 | | // sign = "+" | "-" ; |
6595 | | // END = ? anything not in digit ? |
6596 | | // digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" ; |
6597 | | // integer = [sign] , digit , {digit} ; |
6598 | | // decimal = integer , ["." , integer] ; |
6599 | | // float = ( decimal , END ) | ( decimal , ("E" | "e") , integer , END ) ; |
6600 | | // |
6601 | | // Valid strings are for example: |
6602 | | // -0 +3.1417e+2 -0.0E-3 1.0324 -1.41 11e2 |
6603 | | // |
6604 | | // If the parsing is a success, result is set to the parsed value and true |
6605 | | // is returned. |
6606 | | // |
6607 | | // The function is greedy and will parse until any of the following happens: |
6608 | | // - a non-conforming character is encountered. |
6609 | | // - s_end is reached. |
6610 | | // |
6611 | | // The following situations triggers a failure: |
6612 | | // - s >= s_end. |
6613 | | // - parse failure. |
6614 | | // |
6615 | | static bool tryParseDouble(const char *s, const char *s_end, double *result) { |
6616 | | if (s >= s_end) { |
6617 | | return false; |
6618 | | } |
6619 | | |
6620 | | double mantissa = 0.0; |
6621 | | // This exponent is base 2 rather than 10. |
6622 | | // However the exponent we parse is supposed to be one of ten, |
6623 | | // thus we must take care to convert the exponent/and or the |
6624 | | // mantissa to a * 2^E, where a is the mantissa and E is the |
6625 | | // exponent. |
6626 | | // To get the final double we will use ldexp, it requires the |
6627 | | // exponent to be in base 2. |
6628 | | int exponent = 0; |
6629 | | |
6630 | | // NOTE: THESE MUST BE DECLARED HERE SINCE WE ARE NOT ALLOWED |
6631 | | // TO JUMP OVER DEFINITIONS. |
6632 | | char sign = '+'; |
6633 | | char exp_sign = '+'; |
6634 | | char const *curr = s; |
6635 | | |
6636 | | // How many characters were read in a loop. |
6637 | | int read = 0; |
6638 | | // Tells whether a loop terminated due to reaching s_end. |
6639 | | bool end_not_reached = false; |
6640 | | bool leading_decimal_dots = false; |
6641 | | |
6642 | | /* |
6643 | | BEGIN PARSING. |
6644 | | */ |
6645 | | |
6646 | | // Find out what sign we've got. |
6647 | | if (*curr == '+' || *curr == '-') { |
6648 | | sign = *curr; |
6649 | | curr++; |
6650 | | if ((curr != s_end) && (*curr == '.')) { |
6651 | | // accept. Somethig like `.7e+2`, `-.5234` |
6652 | | leading_decimal_dots = true; |
6653 | | } |
6654 | | } else if (IS_DIGIT(*curr)) { /* Pass through. */ |
6655 | | } else if (*curr == '.') { |
6656 | | // accept. Somethig like `.7e+2`, `-.5234` |
6657 | | leading_decimal_dots = true; |
6658 | | } else { |
6659 | | goto fail; |
6660 | | } |
6661 | | |
6662 | | // Read the integer part. |
6663 | | end_not_reached = (curr != s_end); |
6664 | | if (!leading_decimal_dots) { |
6665 | | while (end_not_reached && IS_DIGIT(*curr)) { |
6666 | | mantissa *= 10; |
6667 | | mantissa += static_cast<int>(*curr - 0x30); |
6668 | | curr++; |
6669 | | read++; |
6670 | | end_not_reached = (curr != s_end); |
6671 | | } |
6672 | | |
6673 | | // We must make sure we actually got something. |
6674 | | if (read == 0) goto fail; |
6675 | | } |
6676 | | |
6677 | | // We allow numbers of form "#", "###" etc. |
6678 | | if (!end_not_reached) goto assemble; |
6679 | | |
6680 | | // Read the decimal part. |
6681 | | if (*curr == '.') { |
6682 | | curr++; |
6683 | | read = 1; |
6684 | | end_not_reached = (curr != s_end); |
6685 | | while (end_not_reached && IS_DIGIT(*curr)) { |
6686 | | static const double pow_lut[] = { |
6687 | | 1.0, 0.1, 0.01, 0.001, 0.0001, 0.00001, 0.000001, 0.0000001, |
6688 | | }; |
6689 | | const int lut_entries = sizeof pow_lut / sizeof pow_lut[0]; |
6690 | | |
6691 | | // NOTE: Don't use powf here, it will absolutely murder precision. |
6692 | | mantissa += static_cast<int>(*curr - 0x30) * |
6693 | | (read < lut_entries ? pow_lut[read] : std::pow(10.0, -read)); |
6694 | | read++; |
6695 | | curr++; |
6696 | | end_not_reached = (curr != s_end); |
6697 | | } |
6698 | | } else if (*curr == 'e' || *curr == 'E') { |
6699 | | } else { |
6700 | | goto assemble; |
6701 | | } |
6702 | | |
6703 | | if (!end_not_reached) goto assemble; |
6704 | | |
6705 | | // Read the exponent part. |
6706 | | if (*curr == 'e' || *curr == 'E') { |
6707 | | curr++; |
6708 | | // Figure out if a sign is present and if it is. |
6709 | | end_not_reached = (curr != s_end); |
6710 | | if (end_not_reached && (*curr == '+' || *curr == '-')) { |
6711 | | exp_sign = *curr; |
6712 | | curr++; |
6713 | | } else if (IS_DIGIT(*curr)) { /* Pass through. */ |
6714 | | } else { |
6715 | | // Empty E is not allowed. |
6716 | | goto fail; |
6717 | | } |
6718 | | |
6719 | | read = 0; |
6720 | | end_not_reached = (curr != s_end); |
6721 | | while (end_not_reached && IS_DIGIT(*curr)) { |
6722 | | // To avoid annoying MSVC's min/max macro definiton, |
6723 | | // Use hardcoded int max value |
6724 | | if (exponent > |
6725 | | ((2147483647 - 9) / 10)) { // (INT_MAX - 9) / 10, guards both multiply and add |
6726 | | // Integer overflow |
6727 | | goto fail; |
6728 | | } |
6729 | | exponent *= 10; |
6730 | | exponent += static_cast<int>(*curr - 0x30); |
6731 | | curr++; |
6732 | | read++; |
6733 | | end_not_reached = (curr != s_end); |
6734 | | } |
6735 | | exponent *= (exp_sign == '+' ? 1 : -1); |
6736 | | if (read == 0) goto fail; |
6737 | | } |
6738 | | |
6739 | | assemble: |
6740 | | *result = (sign == '+' ? 1 : -1) * |
6741 | | (exponent ? std::ldexp(mantissa * std::pow(5.0, exponent), exponent) |
6742 | | : mantissa); |
6743 | | return true; |
6744 | | fail: |
6745 | | return false; |
6746 | | } |
6747 | | |
6748 | | static inline real_t parseReal(const char **token, double default_value = 0.0) { |
6749 | | (*token) += strspn((*token), " \t"); |
6750 | | const char *end = (*token) + strcspn((*token), " \t\r"); |
6751 | | double val = default_value; |
6752 | | tryParseDouble((*token), end, &val); |
6753 | | real_t f = static_cast<real_t>(val); |
6754 | | (*token) = end; |
6755 | | return f; |
6756 | | } |
6757 | | |
6758 | | static inline bool parseReal(const char **token, real_t *out) { |
6759 | | (*token) += strspn((*token), " \t"); |
6760 | | const char *end = (*token) + strcspn((*token), " \t\r"); |
6761 | | double val; |
6762 | | bool ret = tryParseDouble((*token), end, &val); |
6763 | | if (ret) { |
6764 | | real_t f = static_cast<real_t>(val); |
6765 | | (*out) = f; |
6766 | | } |
6767 | | (*token) = end; |
6768 | | return ret; |
6769 | | } |
6770 | | |
6771 | | #endif // TINYOBJLOADER_DISABLE_FAST_FLOAT |
6772 | | |
6773 | | static inline void parseReal2(real_t *x, real_t *y, const char **token, |
6774 | | const double default_x = 0.0, |
6775 | 11.1k | const double default_y = 0.0) { |
6776 | 11.1k | (*x) = parseReal(token, default_x); |
6777 | 11.1k | (*y) = parseReal(token, default_y); |
6778 | 11.1k | } |
6779 | | |
6780 | | static inline void parseReal3(real_t *x, real_t *y, real_t *z, |
6781 | | const char **token, const double default_x = 0.0, |
6782 | | const double default_y = 0.0, |
6783 | 46.2k | const double default_z = 0.0) { |
6784 | 46.2k | (*x) = parseReal(token, default_x); |
6785 | 46.2k | (*y) = parseReal(token, default_y); |
6786 | 46.2k | (*z) = parseReal(token, default_z); |
6787 | 46.2k | } |
6788 | | |
6789 | | #if 0 // not used |
6790 | | static inline void parseV(real_t *x, real_t *y, real_t *z, real_t *w, |
6791 | | const char **token, const double default_x = 0.0, |
6792 | | const double default_y = 0.0, |
6793 | | const double default_z = 0.0, |
6794 | | const double default_w = 1.0) { |
6795 | | (*x) = parseReal(token, default_x); |
6796 | | (*y) = parseReal(token, default_y); |
6797 | | (*z) = parseReal(token, default_z); |
6798 | | (*w) = parseReal(token, default_w); |
6799 | | } |
6800 | | #endif |
6801 | | |
6802 | | // Extension: parse vertex with colors(6 items) |
6803 | | // Return 3: xyz, 4: xyzw, 6: xyzrgb |
6804 | | // `r`: red(case 6) or [w](case 4) |
6805 | | // NOTE: This classic path caps at 6 components per the de-facto vertex-color |
6806 | | // extension (xyz / xyzw / xyzrgb; weight and color are mutually exclusive) and |
6807 | | // ignores any further tokens. LoadObjOpt / the experimental stream loader |
6808 | | // additionally accept a non-standard 7-component `v x y z w r g b` (weight + |
6809 | | // color) form, so they diverge from this function for 7+ token vertices. |
6810 | | static inline int parseVertexWithColor(real_t *x, real_t *y, real_t *z, |
6811 | | real_t *r, real_t *g, real_t *b, |
6812 | | const char **token, |
6813 | | const double default_x = 0.0, |
6814 | | const double default_y = 0.0, |
6815 | 0 | const double default_z = 0.0) { |
6816 | 0 | // TODO: Check error |
6817 | 0 | (*x) = parseReal(token, default_x); |
6818 | 0 | (*y) = parseReal(token, default_y); |
6819 | 0 | (*z) = parseReal(token, default_z); |
6820 | 0 |
|
6821 | 0 | // - 4 components(x, y, z, w) ot 6 components |
6822 | 0 | bool has_r = parseReal(token, r); |
6823 | 0 |
|
6824 | 0 | if (!has_r) { |
6825 | 0 | (*r) = (*g) = (*b) = 1.0; |
6826 | 0 | return 3; |
6827 | 0 | } |
6828 | 0 |
|
6829 | 0 | bool has_g = parseReal(token, g); |
6830 | 0 |
|
6831 | 0 | if (!has_g) { |
6832 | 0 | (*g) = (*b) = 1.0; |
6833 | 0 | return 4; |
6834 | 0 | } |
6835 | 0 |
|
6836 | 0 | bool has_b = parseReal(token, b); |
6837 | 0 |
|
6838 | 0 | if (!has_b) { |
6839 | 0 | (*r) = (*g) = (*b) = 1.0; |
6840 | 0 | return 3; // treated as xyz |
6841 | 0 | } |
6842 | 0 |
|
6843 | 0 | return 6; |
6844 | 0 | } |
6845 | | |
6846 | 41.8k | static inline bool parseOnOff(const char **token, bool default_value = true) { |
6847 | 41.8k | (*token) += strspn((*token), " \t"); |
6848 | 41.8k | const char *end = (*token) + strcspn((*token), " \t\r"); |
6849 | | |
6850 | 41.8k | bool ret = default_value; |
6851 | 41.8k | if ((0 == strncmp((*token), "on", 2))) { |
6852 | 12.6k | ret = true; |
6853 | 29.2k | } else if ((0 == strncmp((*token), "off", 3))) { |
6854 | 17.0k | ret = false; |
6855 | 17.0k | } |
6856 | | |
6857 | 41.8k | (*token) = end; |
6858 | 41.8k | return ret; |
6859 | 41.8k | } |
6860 | | |
6861 | | static inline texture_type_t parseTextureType( |
6862 | 12.3k | const char **token, texture_type_t default_value = TEXTURE_TYPE_NONE) { |
6863 | 12.3k | (*token) += strspn((*token), " \t"); |
6864 | 12.3k | const char *end = (*token) + strcspn((*token), " \t\r"); |
6865 | 12.3k | texture_type_t ty = default_value; |
6866 | | |
6867 | 12.3k | if ((0 == strncmp((*token), "cube_top", strlen("cube_top")))) { |
6868 | 2.69k | ty = TEXTURE_TYPE_CUBE_TOP; |
6869 | 9.65k | } else if ((0 == strncmp((*token), "cube_bottom", strlen("cube_bottom")))) { |
6870 | 379 | ty = TEXTURE_TYPE_CUBE_BOTTOM; |
6871 | 9.28k | } else if ((0 == strncmp((*token), "cube_left", strlen("cube_left")))) { |
6872 | 2.48k | ty = TEXTURE_TYPE_CUBE_LEFT; |
6873 | 6.80k | } else if ((0 == strncmp((*token), "cube_right", strlen("cube_right")))) { |
6874 | 247 | ty = TEXTURE_TYPE_CUBE_RIGHT; |
6875 | 6.55k | } else if ((0 == strncmp((*token), "cube_front", strlen("cube_front")))) { |
6876 | 229 | ty = TEXTURE_TYPE_CUBE_FRONT; |
6877 | 6.32k | } else if ((0 == strncmp((*token), "cube_back", strlen("cube_back")))) { |
6878 | 1.01k | ty = TEXTURE_TYPE_CUBE_BACK; |
6879 | 5.30k | } else if ((0 == strncmp((*token), "sphere", strlen("sphere")))) { |
6880 | 354 | ty = TEXTURE_TYPE_SPHERE; |
6881 | 354 | } |
6882 | | |
6883 | 12.3k | (*token) = end; |
6884 | 12.3k | return ty; |
6885 | 12.3k | } |
6886 | | |
6887 | 0 | static tag_sizes parseTagTriple(const char **token) { |
6888 | 0 | tag_sizes ts; |
6889 | 0 |
|
6890 | 0 | (*token) += strspn((*token), " \t"); |
6891 | 0 | ts.num_ints = atoi((*token)); |
6892 | 0 | (*token) += strcspn((*token), "/ \t\r"); |
6893 | 0 | if ((*token)[0] != '/') { |
6894 | 0 | return ts; |
6895 | 0 | } |
6896 | 0 |
|
6897 | 0 | (*token)++; // Skip '/' |
6898 | 0 |
|
6899 | 0 | (*token) += strspn((*token), " \t"); |
6900 | 0 | ts.num_reals = atoi((*token)); |
6901 | 0 | (*token) += strcspn((*token), "/ \t\r"); |
6902 | 0 | if ((*token)[0] != '/') { |
6903 | 0 | return ts; |
6904 | 0 | } |
6905 | 0 | (*token)++; // Skip '/' |
6906 | 0 |
|
6907 | 0 | ts.num_strings = parseInt(token); |
6908 | 0 |
|
6909 | 0 | return ts; |
6910 | 0 | } |
6911 | | |
6912 | | // Parse triples with index offsets: i, i/j/k, i//k, i/j |
6913 | | static bool parseTriple(const char **token, int vsize, int vnsize, int vtsize, |
6914 | 0 | vertex_index_t *ret, const warning_context &context) { |
6915 | 0 | if (!ret) { |
6916 | 0 | return false; |
6917 | 0 | } |
6918 | 0 |
|
6919 | 0 | vertex_index_t vi(-1); |
6920 | 0 |
|
6921 | 0 | if (!fixIndex(atoi((*token)), vsize, &vi.v_idx, false, context)) { |
6922 | 0 | return false; |
6923 | 0 | } |
6924 | 0 |
|
6925 | 0 | (*token) += strcspn((*token), "/ \t\r"); |
6926 | 0 | if ((*token)[0] != '/') { |
6927 | 0 | (*ret) = vi; |
6928 | 0 | return true; |
6929 | 0 | } |
6930 | 0 | (*token)++; |
6931 | 0 |
|
6932 | 0 | // i//k |
6933 | 0 | if ((*token)[0] == '/') { |
6934 | 0 | (*token)++; |
6935 | 0 | if (!fixIndex(atoi((*token)), vnsize, &vi.vn_idx, true, context)) { |
6936 | 0 | return false; |
6937 | 0 | } |
6938 | 0 | (*token) += strcspn((*token), "/ \t\r"); |
6939 | 0 | (*ret) = vi; |
6940 | 0 | return true; |
6941 | 0 | } |
6942 | 0 |
|
6943 | 0 | // i/j/k or i/j |
6944 | 0 | if (!fixIndex(atoi((*token)), vtsize, &vi.vt_idx, true, context)) { |
6945 | 0 | return false; |
6946 | 0 | } |
6947 | 0 |
|
6948 | 0 | (*token) += strcspn((*token), "/ \t\r"); |
6949 | 0 | if ((*token)[0] != '/') { |
6950 | 0 | (*ret) = vi; |
6951 | 0 | return true; |
6952 | 0 | } |
6953 | 0 |
|
6954 | 0 | // i/j/k |
6955 | 0 | (*token)++; // skip '/' |
6956 | 0 | if (!fixIndex(atoi((*token)), vnsize, &vi.vn_idx, true, context)) { |
6957 | 0 | return false; |
6958 | 0 | } |
6959 | 0 | (*token) += strcspn((*token), "/ \t\r"); |
6960 | 0 |
|
6961 | 0 | (*ret) = vi; |
6962 | 0 |
|
6963 | 0 | return true; |
6964 | 0 | } |
6965 | | |
6966 | | // Parse raw triples: i, i/j/k, i//k, i/j |
6967 | 0 | static vertex_index_t parseRawTriple(const char **token) { |
6968 | 0 | vertex_index_t vi(static_cast<int>(0)); // 0 is an invalid index in OBJ |
6969 | 0 |
|
6970 | 0 | vi.v_idx = atoi((*token)); |
6971 | 0 | (*token) += strcspn((*token), "/ \t\r"); |
6972 | 0 | if ((*token)[0] != '/') { |
6973 | 0 | return vi; |
6974 | 0 | } |
6975 | 0 | (*token)++; |
6976 | 0 |
|
6977 | 0 | // i//k |
6978 | 0 | if ((*token)[0] == '/') { |
6979 | 0 | (*token)++; |
6980 | 0 | vi.vn_idx = atoi((*token)); |
6981 | 0 | (*token) += strcspn((*token), "/ \t\r"); |
6982 | 0 | return vi; |
6983 | 0 | } |
6984 | 0 |
|
6985 | 0 | // i/j/k or i/j |
6986 | 0 | vi.vt_idx = atoi((*token)); |
6987 | 0 | (*token) += strcspn((*token), "/ \t\r"); |
6988 | 0 | if ((*token)[0] != '/') { |
6989 | 0 | return vi; |
6990 | 0 | } |
6991 | 0 |
|
6992 | 0 | // i/j/k |
6993 | 0 | (*token)++; // skip '/' |
6994 | 0 | vi.vn_idx = atoi((*token)); |
6995 | 0 | (*token) += strcspn((*token), "/ \t\r"); |
6996 | 0 | return vi; |
6997 | 0 | } |
6998 | | |
6999 | | // --- Stream-based parse functions --- |
7000 | | |
7001 | 8.33M | static inline std::string sr_parseString(StreamReader &sr) { |
7002 | 8.33M | sr.skip_space(); |
7003 | 8.33M | std::string s; |
7004 | 24.1M | while (!sr.eof()) { |
7005 | 23.9M | char c = sr.peek(); |
7006 | 23.9M | if (c == ' ' || c == '\t' || c == '\r' || c == '\n' || c == '\0') break; |
7007 | 15.7M | s += c; |
7008 | 15.7M | sr.advance(1); |
7009 | 15.7M | } |
7010 | 8.33M | return s; |
7011 | 8.33M | } |
7012 | | |
7013 | 6.22M | static inline int sr_parseInt(StreamReader &sr) { |
7014 | 6.22M | sr.skip_space(); |
7015 | 6.22M | const char *start = sr.current_ptr(); |
7016 | 6.22M | size_t rem = sr.remaining(); |
7017 | 6.22M | size_t len = 0; |
7018 | 12.5M | while (len < rem) { |
7019 | 12.1M | char c = start[len]; |
7020 | 12.1M | if (c == ' ' || c == '\t' || c == '\r' || c == '\n' || c == '\0') break; |
7021 | 6.32M | len++; |
7022 | 6.32M | } |
7023 | 6.22M | int i = 0; |
7024 | 6.22M | if (len > 0) { |
7025 | 45.1k | char tmp[64]; |
7026 | 45.1k | size_t copy_len = len < 63 ? len : 63; |
7027 | 45.1k | if (copy_len != len) { |
7028 | 3.92k | sr.advance(len); |
7029 | 3.92k | return 0; |
7030 | 3.92k | } |
7031 | 41.2k | memcpy(tmp, start, copy_len); |
7032 | 41.2k | tmp[copy_len] = '\0'; |
7033 | 41.2k | errno = 0; |
7034 | 41.2k | char *endptr = NULL; |
7035 | 41.2k | long val = strtol(tmp, &endptr, 10); |
7036 | 41.2k | const bool has_error = |
7037 | 41.2k | (errno == ERANGE || endptr == tmp || |
7038 | 23.2k | val > (std::numeric_limits<int>::max)() || |
7039 | 22.9k | val < (std::numeric_limits<int>::min)()); |
7040 | 41.2k | if (!has_error) { |
7041 | 22.9k | i = static_cast<int>(val); |
7042 | 22.9k | } |
7043 | 41.2k | } |
7044 | 6.22M | sr.advance(len); |
7045 | 6.22M | return i; |
7046 | 6.22M | } |
7047 | | |
7048 | 15.2M | static inline real_t sr_parseReal(StreamReader &sr, double default_value = 0.0) { |
7049 | 15.2M | sr.skip_space(); |
7050 | 15.2M | const char *start = sr.current_ptr(); |
7051 | 15.2M | size_t rem = sr.remaining(); |
7052 | 15.2M | size_t len = 0; |
7053 | 25.2M | while (len < rem) { |
7054 | 24.7M | char c = start[len]; |
7055 | 24.7M | if (c == ' ' || c == '\t' || c == '\r' || c == '\n' || c == '\0') break; |
7056 | 9.98M | len++; |
7057 | 9.98M | } |
7058 | 15.2M | double val = default_value; |
7059 | 15.2M | if (len > 0) { |
7060 | 1.29M | tryParseDouble(start, start + len, &val); |
7061 | 1.29M | } |
7062 | 15.2M | sr.advance(len); |
7063 | 15.2M | return static_cast<real_t>(val); |
7064 | 15.2M | } |
7065 | | |
7066 | 1.29M | static inline bool sr_parseReal(StreamReader &sr, real_t *out) { |
7067 | 1.29M | sr.skip_space(); |
7068 | 1.29M | const char *start = sr.current_ptr(); |
7069 | 1.29M | size_t rem = sr.remaining(); |
7070 | 1.29M | size_t len = 0; |
7071 | 2.94M | while (len < rem) { |
7072 | 2.94M | char c = start[len]; |
7073 | 2.94M | if (c == ' ' || c == '\t' || c == '\r' || c == '\n' || c == '\0') break; |
7074 | 1.65M | len++; |
7075 | 1.65M | } |
7076 | 1.29M | if (len == 0) return false; |
7077 | 106k | double val; |
7078 | 106k | bool ret = tryParseDouble(start, start + len, &val); |
7079 | 106k | if (ret) { |
7080 | 72.4k | (*out) = static_cast<real_t>(val); |
7081 | 72.4k | } |
7082 | 106k | sr.advance(len); |
7083 | 106k | return ret; |
7084 | 1.29M | } |
7085 | | |
7086 | | static inline void sr_parseReal2(real_t *x, real_t *y, StreamReader &sr, |
7087 | | const double default_x = 0.0, |
7088 | 621k | const double default_y = 0.0) { |
7089 | 621k | (*x) = sr_parseReal(sr, default_x); |
7090 | 621k | (*y) = sr_parseReal(sr, default_y); |
7091 | 621k | } |
7092 | | |
7093 | | static inline void sr_parseReal3(real_t *x, real_t *y, real_t *z, |
7094 | | StreamReader &sr, |
7095 | | const double default_x = 0.0, |
7096 | | const double default_y = 0.0, |
7097 | 0 | const double default_z = 0.0) { |
7098 | 0 | (*x) = sr_parseReal(sr, default_x); |
7099 | 0 | (*y) = sr_parseReal(sr, default_y); |
7100 | 0 | (*z) = sr_parseReal(sr, default_z); |
7101 | 0 | } |
7102 | | |
7103 | | static inline int sr_parseVertexWithColor(real_t *x, real_t *y, real_t *z, |
7104 | | real_t *r, real_t *g, real_t *b, |
7105 | | StreamReader &sr, |
7106 | | const double default_x = 0.0, |
7107 | | const double default_y = 0.0, |
7108 | 0 | const double default_z = 0.0) { |
7109 | 0 | (*x) = sr_parseReal(sr, default_x); |
7110 | 0 | (*y) = sr_parseReal(sr, default_y); |
7111 | 0 | (*z) = sr_parseReal(sr, default_z); |
7112 | 0 |
|
7113 | 0 | bool has_r = sr_parseReal(sr, r); |
7114 | 0 | if (!has_r) { |
7115 | 0 | (*r) = (*g) = (*b) = 1.0; |
7116 | 0 | return 3; |
7117 | 0 | } |
7118 | 0 |
|
7119 | 0 | bool has_g = sr_parseReal(sr, g); |
7120 | 0 | if (!has_g) { |
7121 | 0 | (*g) = (*b) = 1.0; |
7122 | 0 | return 4; |
7123 | 0 | } |
7124 | 0 |
|
7125 | 0 | bool has_b = sr_parseReal(sr, b); |
7126 | 0 | if (!has_b) { |
7127 | 0 | (*r) = (*g) = (*b) = 1.0; |
7128 | 0 | return 3; |
7129 | 0 | } |
7130 | 0 |
|
7131 | 0 | return 6; |
7132 | 0 | } |
7133 | | |
7134 | | // --- Error-reporting overloads --- |
7135 | | // These overloads push clang-style diagnostics into `err` when parsing fails |
7136 | | // and return false so callers can early-return on unrecoverable parse errors. |
7137 | | // The original signatures are preserved above for backward compatibility. |
7138 | | |
7139 | | static inline bool sr_parseInt(StreamReader &sr, int *out, std::string *err, |
7140 | 85.6k | const std::string &filename) { |
7141 | 85.6k | sr.skip_space(); |
7142 | 85.6k | const char *start = sr.current_ptr(); |
7143 | 85.6k | size_t rem = sr.remaining(); |
7144 | 85.6k | size_t len = 0; |
7145 | 1.85M | while (len < rem) { |
7146 | 1.85M | char c = start[len]; |
7147 | 1.85M | if (c == ' ' || c == '\t' || c == '\r' || c == '\n' || c == '\0') break; |
7148 | 1.76M | len++; |
7149 | 1.76M | } |
7150 | 85.6k | if (len == 0) { |
7151 | 34 | if (err) { |
7152 | 34 | (*err) += sr.format_error(filename, "expected integer value"); |
7153 | 34 | } |
7154 | 34 | *out = 0; |
7155 | 34 | return false; |
7156 | 34 | } |
7157 | 85.6k | char tmp[64]; |
7158 | 85.6k | size_t copy_len = len < 63 ? len : 63; |
7159 | 85.6k | memcpy(tmp, start, copy_len); |
7160 | 85.6k | tmp[copy_len] = '\0'; |
7161 | 85.6k | if (copy_len != len) { |
7162 | 38 | if (err) { |
7163 | 38 | (*err) += sr.format_error(filename, "integer value too long"); |
7164 | 38 | } |
7165 | 38 | *out = 0; |
7166 | 38 | sr.advance(len); |
7167 | 38 | return false; |
7168 | 38 | } |
7169 | 85.6k | errno = 0; |
7170 | 85.6k | char *endptr = NULL; |
7171 | 85.6k | long val = strtol(tmp, &endptr, 10); |
7172 | 85.6k | if (errno == ERANGE || val > (std::numeric_limits<int>::max)() || |
7173 | 85.5k | val < (std::numeric_limits<int>::min)()) { |
7174 | 66 | if (err) { |
7175 | 66 | (*err) += sr.format_error(filename, |
7176 | 66 | "integer value out of range, got '" + std::string(tmp) + "'"); |
7177 | 66 | } |
7178 | 66 | *out = 0; |
7179 | 66 | sr.advance(len); |
7180 | 66 | return false; |
7181 | 66 | } |
7182 | 85.5k | if (endptr == tmp || (*endptr != '\0' && *endptr != ' ' && *endptr != '\t')) { |
7183 | 73 | if (err) { |
7184 | 73 | (*err) += sr.format_error(filename, |
7185 | 73 | "expected integer, got '" + std::string(tmp) + "'"); |
7186 | 73 | } |
7187 | 73 | *out = 0; |
7188 | 73 | sr.advance(len); |
7189 | 73 | return false; |
7190 | 73 | } |
7191 | 85.4k | *out = static_cast<int>(val); |
7192 | 85.4k | sr.advance(len); |
7193 | 85.4k | return true; |
7194 | 85.5k | } |
7195 | | |
7196 | | static inline bool sr_parseReal(StreamReader &sr, real_t *out, |
7197 | | double default_value, |
7198 | | std::string *err, |
7199 | 5.34M | const std::string &filename) { |
7200 | 5.34M | sr.skip_space(); |
7201 | 5.34M | const char *start = sr.current_ptr(); |
7202 | 5.34M | size_t rem = sr.remaining(); |
7203 | 5.34M | size_t len = 0; |
7204 | 20.3M | while (len < rem) { |
7205 | 20.3M | char c = start[len]; |
7206 | 20.3M | if (c == ' ' || c == '\t' || c == '\r' || c == '\n' || c == '\0') break; |
7207 | 15.0M | len++; |
7208 | 15.0M | } |
7209 | 5.34M | if (len == 0) { |
7210 | | // No token to parse — not necessarily an error (e.g. optional component). |
7211 | 4.97M | *out = static_cast<real_t>(default_value); |
7212 | 4.97M | return true; |
7213 | 4.97M | } |
7214 | 367k | double val; |
7215 | 367k | if (!tryParseDouble(start, start + len, &val)) { |
7216 | 610 | if (err) { |
7217 | 610 | char tmp[64]; |
7218 | 610 | size_t copy_len = len < 63 ? len : 63; |
7219 | 610 | memcpy(tmp, start, copy_len); |
7220 | 610 | tmp[copy_len] = '\0'; |
7221 | 610 | (*err) += sr.format_error(filename, |
7222 | 610 | "expected number, got '" + std::string(tmp) + "'"); |
7223 | 610 | } |
7224 | 610 | *out = static_cast<real_t>(default_value); |
7225 | 610 | sr.advance(len); |
7226 | 610 | return false; |
7227 | 610 | } |
7228 | 366k | *out = static_cast<real_t>(val); |
7229 | 366k | sr.advance(len); |
7230 | 366k | return true; |
7231 | 367k | } |
7232 | | |
7233 | | static inline bool sr_parseReal2(real_t *x, real_t *y, StreamReader &sr, |
7234 | | std::string *err, |
7235 | | const std::string &filename, |
7236 | | const double default_x = 0.0, |
7237 | 359k | const double default_y = 0.0) { |
7238 | 359k | if (!sr_parseReal(sr, x, default_x, err, filename)) return false; |
7239 | 359k | if (!sr_parseReal(sr, y, default_y, err, filename)) return false; |
7240 | 359k | return true; |
7241 | 359k | } |
7242 | | |
7243 | | static inline bool sr_parseReal3(real_t *x, real_t *y, real_t *z, |
7244 | | StreamReader &sr, |
7245 | | std::string *err, |
7246 | | const std::string &filename, |
7247 | | const double default_x = 0.0, |
7248 | | const double default_y = 0.0, |
7249 | 614k | const double default_z = 0.0) { |
7250 | 614k | if (!sr_parseReal(sr, x, default_x, err, filename)) return false; |
7251 | 614k | if (!sr_parseReal(sr, y, default_y, err, filename)) return false; |
7252 | 614k | if (!sr_parseReal(sr, z, default_z, err, filename)) return false; |
7253 | 614k | return true; |
7254 | 614k | } |
7255 | | |
7256 | | // Returns number of components parsed (3, 4, or 6) on success, -1 on error. |
7257 | | static inline int sr_parseVertexWithColor(real_t *x, real_t *y, real_t *z, |
7258 | | real_t *r, real_t *g, real_t *b, |
7259 | | StreamReader &sr, |
7260 | | std::string *err, |
7261 | | const std::string &filename, |
7262 | | const double default_x = 0.0, |
7263 | | const double default_y = 0.0, |
7264 | 888k | const double default_z = 0.0) { |
7265 | 888k | if (!sr_parseReal(sr, x, default_x, err, filename)) return -1; |
7266 | 888k | if (!sr_parseReal(sr, y, default_y, err, filename)) return -1; |
7267 | 888k | if (!sr_parseReal(sr, z, default_z, err, filename)) return -1; |
7268 | | |
7269 | 888k | bool has_r = sr_parseReal(sr, r); |
7270 | 888k | if (!has_r) { |
7271 | 859k | (*r) = (*g) = (*b) = 1.0; |
7272 | 859k | return 3; |
7273 | 859k | } |
7274 | | |
7275 | 29.1k | bool has_g = sr_parseReal(sr, g); |
7276 | 29.1k | if (!has_g) { |
7277 | 10.4k | (*g) = (*b) = 1.0; |
7278 | 10.4k | return 4; |
7279 | 10.4k | } |
7280 | | |
7281 | 18.7k | bool has_b = sr_parseReal(sr, b); |
7282 | 18.7k | if (!has_b) { |
7283 | 9.13k | (*r) = (*g) = (*b) = 1.0; |
7284 | 9.13k | return 3; |
7285 | 9.13k | } |
7286 | | |
7287 | 9.60k | return 6; |
7288 | 18.7k | } |
7289 | | |
7290 | | static inline int sr_parseIntNoSkip(StreamReader &sr); |
7291 | | |
7292 | | // Advance past remaining characters in a tag triple field (stops at '/', whitespace, or line end). |
7293 | 988k | static inline void sr_skipTagField(StreamReader &sr) { |
7294 | 2.21M | while (!sr.eof() && !sr.at_line_end() && !IS_SPACE(sr.peek()) && |
7295 | 1.32M | sr.peek() != '/') { |
7296 | 1.22M | sr.advance(1); |
7297 | 1.22M | } |
7298 | 988k | } |
7299 | | |
7300 | 903k | static tag_sizes sr_parseTagTriple(StreamReader &sr) { |
7301 | 903k | tag_sizes ts; |
7302 | | |
7303 | 903k | sr.skip_space(); |
7304 | 903k | ts.num_ints = sr_parseIntNoSkip(sr); |
7305 | 903k | sr_skipTagField(sr); |
7306 | 903k | if (!sr.eof() && sr.peek() == '/') { |
7307 | 85.5k | sr.advance(1); |
7308 | 85.5k | sr.skip_space(); |
7309 | 85.5k | ts.num_reals = sr_parseIntNoSkip(sr); |
7310 | 85.5k | sr_skipTagField(sr); |
7311 | 85.5k | if (!sr.eof() && sr.peek() == '/') { |
7312 | 11.9k | sr.advance(1); |
7313 | 11.9k | ts.num_strings = sr_parseInt(sr); |
7314 | 11.9k | } |
7315 | 85.5k | } |
7316 | 903k | return ts; |
7317 | 903k | } |
7318 | | |
7319 | 5.98M | static inline int sr_parseIntNoSkip(StreamReader &sr) { |
7320 | 5.98M | const char *start = sr.current_ptr(); |
7321 | 5.98M | size_t rem = sr.remaining(); |
7322 | 5.98M | size_t len = 0; |
7323 | 5.98M | if (len < rem && (start[len] == '+' || start[len] == '-')) len++; |
7324 | 11.9M | while (len < rem && start[len] >= '0' && start[len] <= '9') len++; |
7325 | 5.98M | int i = 0; |
7326 | 5.98M | if (len > 0) { |
7327 | 5.13M | char tmp[64]; |
7328 | 5.13M | size_t copy_len = len < 63 ? len : 63; |
7329 | 5.13M | if (copy_len != len) { |
7330 | 1.27k | sr.advance(len); |
7331 | 1.27k | return 0; |
7332 | 1.27k | } |
7333 | 5.13M | memcpy(tmp, start, copy_len); |
7334 | 5.13M | tmp[copy_len] = '\0'; |
7335 | 5.13M | errno = 0; |
7336 | 5.13M | char *endptr = NULL; |
7337 | 5.13M | long val = strtol(tmp, &endptr, 10); |
7338 | 5.13M | if (errno == 0 && endptr != tmp && *endptr == '\0' && |
7339 | 5.12M | val <= (std::numeric_limits<int>::max)() && |
7340 | 5.11M | val >= (std::numeric_limits<int>::min)()) { |
7341 | 5.11M | i = static_cast<int>(val); |
7342 | 5.11M | } |
7343 | 5.13M | } |
7344 | 5.98M | sr.advance(len); |
7345 | 5.98M | return i; |
7346 | 5.98M | } |
7347 | | |
7348 | 4.99M | static inline void sr_skipUntil(StreamReader &sr, const char *delims) { |
7349 | 7.38M | while (!sr.eof()) { |
7350 | 7.38M | char c = sr.peek(); |
7351 | 29.7M | for (const char *d = delims; *d; d++) { |
7352 | 27.3M | if (c == *d) return; |
7353 | 27.3M | } |
7354 | 2.39M | sr.advance(1); |
7355 | 2.39M | } |
7356 | 4.99M | } |
7357 | | |
7358 | | static bool sr_parseTriple(StreamReader &sr, int vsize, int vnsize, int vtsize, |
7359 | 4.97M | vertex_index_t *ret, const warning_context &context) { |
7360 | 4.97M | if (!ret) return false; |
7361 | | |
7362 | 4.97M | vertex_index_t vi(-1); |
7363 | | |
7364 | 4.97M | sr.skip_space(); |
7365 | 4.97M | if (!fixIndex(sr_parseIntNoSkip(sr), vsize, &vi.v_idx, false, context)) { |
7366 | 433 | return false; |
7367 | 433 | } |
7368 | | |
7369 | 4.97M | sr_skipUntil(sr, "/ \t\r\n"); |
7370 | 4.97M | if (sr.eof() || sr.peek() != '/') { |
7371 | 4.95M | (*ret) = vi; |
7372 | 4.95M | return true; |
7373 | 4.95M | } |
7374 | 20.2k | sr.advance(1); |
7375 | | |
7376 | | // i//k |
7377 | 20.2k | if (!sr.eof() && sr.peek() == '/') { |
7378 | 9.22k | sr.advance(1); |
7379 | 9.22k | if (!fixIndex(sr_parseIntNoSkip(sr), vnsize, &vi.vn_idx, true, context)) { |
7380 | 2 | return false; |
7381 | 2 | } |
7382 | 9.22k | sr_skipUntil(sr, "/ \t\r\n"); |
7383 | 9.22k | (*ret) = vi; |
7384 | 9.22k | return true; |
7385 | 9.22k | } |
7386 | | |
7387 | | // i/j/k or i/j |
7388 | 11.0k | if (!fixIndex(sr_parseIntNoSkip(sr), vtsize, &vi.vt_idx, true, context)) { |
7389 | 1 | return false; |
7390 | 1 | } |
7391 | | |
7392 | 11.0k | sr_skipUntil(sr, "/ \t\r\n"); |
7393 | 11.0k | if (sr.eof() || sr.peek() != '/') { |
7394 | 9.92k | (*ret) = vi; |
7395 | 9.92k | return true; |
7396 | 9.92k | } |
7397 | | |
7398 | | // i/j/k |
7399 | 1.10k | sr.advance(1); |
7400 | 1.10k | if (!fixIndex(sr_parseIntNoSkip(sr), vnsize, &vi.vn_idx, true, context)) { |
7401 | 1 | return false; |
7402 | 1 | } |
7403 | 1.10k | sr_skipUntil(sr, "/ \t\r\n"); |
7404 | | |
7405 | 1.10k | (*ret) = vi; |
7406 | 1.10k | return true; |
7407 | 1.10k | } |
7408 | | |
7409 | 0 | static vertex_index_t sr_parseRawTriple(StreamReader &sr) { |
7410 | 0 | vertex_index_t vi(static_cast<int>(0)); |
7411 | |
|
7412 | 0 | sr.skip_space(); |
7413 | 0 | vi.v_idx = sr_parseIntNoSkip(sr); |
7414 | 0 | sr_skipUntil(sr, "/ \t\r\n"); |
7415 | 0 | if (sr.eof() || sr.peek() != '/') return vi; |
7416 | 0 | sr.advance(1); |
7417 | | |
7418 | | // i//k |
7419 | 0 | if (!sr.eof() && sr.peek() == '/') { |
7420 | 0 | sr.advance(1); |
7421 | 0 | vi.vn_idx = sr_parseIntNoSkip(sr); |
7422 | 0 | sr_skipUntil(sr, "/ \t\r\n"); |
7423 | 0 | return vi; |
7424 | 0 | } |
7425 | | |
7426 | | // i/j/k or i/j |
7427 | 0 | vi.vt_idx = sr_parseIntNoSkip(sr); |
7428 | 0 | sr_skipUntil(sr, "/ \t\r\n"); |
7429 | 0 | if (sr.eof() || sr.peek() != '/') return vi; |
7430 | | |
7431 | 0 | sr.advance(1); |
7432 | 0 | vi.vn_idx = sr_parseIntNoSkip(sr); |
7433 | 0 | sr_skipUntil(sr, "/ \t\r\n"); |
7434 | 0 | return vi; |
7435 | 0 | } |
7436 | | |
7437 | | bool ParseTextureNameAndOption(std::string *texname, texture_option_t *texopt, |
7438 | 370k | const char *linebuf) { |
7439 | | // @todo { write more robust lexer and parser. } |
7440 | 370k | bool found_texname = false; |
7441 | 370k | std::string texture_name; |
7442 | | |
7443 | 370k | const char *token = linebuf; // Assume line ends with NULL |
7444 | | |
7445 | 805k | while (!IS_NEW_LINE((*token))) { |
7446 | 434k | token += strspn(token, " \t"); // skip space |
7447 | 434k | if ((0 == strncmp(token, "-blendu", 7)) && IS_SPACE((token[7]))) { |
7448 | 3.88k | token += 8; |
7449 | 3.88k | texopt->blendu = parseOnOff(&token, /* default */ true); |
7450 | 430k | } else if ((0 == strncmp(token, "-blendv", 7)) && IS_SPACE((token[7]))) { |
7451 | 22.3k | token += 8; |
7452 | 22.3k | texopt->blendv = parseOnOff(&token, /* default */ true); |
7453 | 408k | } else if ((0 == strncmp(token, "-clamp", 6)) && IS_SPACE((token[6]))) { |
7454 | 15.6k | token += 7; |
7455 | 15.6k | texopt->clamp = parseOnOff(&token, /* default */ true); |
7456 | 392k | } else if ((0 == strncmp(token, "-boost", 6)) && IS_SPACE((token[6]))) { |
7457 | 11.0k | token += 7; |
7458 | 11.0k | texopt->sharpness = parseReal(&token, 1.0); |
7459 | 381k | } else if ((0 == strncmp(token, "-bm", 3)) && IS_SPACE((token[3]))) { |
7460 | 254 | token += 4; |
7461 | 254 | texopt->bump_multiplier = parseReal(&token, 1.0); |
7462 | 381k | } else if ((0 == strncmp(token, "-o", 2)) && IS_SPACE((token[2]))) { |
7463 | 21.9k | token += 3; |
7464 | 21.9k | parseReal3(&(texopt->origin_offset[0]), &(texopt->origin_offset[1]), |
7465 | 21.9k | &(texopt->origin_offset[2]), &token); |
7466 | 359k | } else if ((0 == strncmp(token, "-s", 2)) && IS_SPACE((token[2]))) { |
7467 | 3.08k | token += 3; |
7468 | 3.08k | parseReal3(&(texopt->scale[0]), &(texopt->scale[1]), &(texopt->scale[2]), |
7469 | 3.08k | &token, 1.0, 1.0, 1.0); |
7470 | 356k | } else if ((0 == strncmp(token, "-t", 2)) && IS_SPACE((token[2]))) { |
7471 | 21.2k | token += 3; |
7472 | 21.2k | parseReal3(&(texopt->turbulence[0]), &(texopt->turbulence[1]), |
7473 | 21.2k | &(texopt->turbulence[2]), &token); |
7474 | 335k | } else if ((0 == strncmp(token, "-type", 5)) && IS_SPACE((token[5]))) { |
7475 | 12.3k | token += 5; |
7476 | 12.3k | texopt->type = parseTextureType((&token), TEXTURE_TYPE_NONE); |
7477 | 322k | } else if ((0 == strncmp(token, "-texres", 7)) && IS_SPACE((token[7]))) { |
7478 | 1.83k | token += 7; |
7479 | | // TODO(syoyo): Check if arg is int type. |
7480 | 1.83k | texopt->texture_resolution = parseInt(&token); |
7481 | 321k | } else if ((0 == strncmp(token, "-imfchan", 8)) && IS_SPACE((token[8]))) { |
7482 | 6.73k | token += 9; |
7483 | 6.73k | token += strspn(token, " \t"); |
7484 | 6.73k | const char *end = token + strcspn(token, " \t\r"); |
7485 | 6.73k | if ((end - token) == 1) { // Assume one char for -imfchan |
7486 | 5.79k | texopt->imfchan = (*token); |
7487 | 5.79k | } |
7488 | 6.73k | token = end; |
7489 | 314k | } else if ((0 == strncmp(token, "-mm", 3)) && IS_SPACE((token[3]))) { |
7490 | 11.1k | token += 4; |
7491 | 11.1k | parseReal2(&(texopt->brightness), &(texopt->contrast), &token, 0.0, 1.0); |
7492 | 303k | } else if ((0 == strncmp(token, "-colorspace", 11)) && |
7493 | 4.64k | IS_SPACE((token[11]))) { |
7494 | 4.00k | token += 12; |
7495 | 4.00k | texopt->colorspace = parseString(&token); |
7496 | 299k | } else { |
7497 | | // Assume texture filename |
7498 | | #if 0 |
7499 | | size_t len = strcspn(token, " \t\r"); // untile next space |
7500 | | texture_name = std::string(token, token + len); |
7501 | | token += len; |
7502 | | |
7503 | | token += strspn(token, " \t"); // skip space |
7504 | | #else |
7505 | | // Read filename until line end to parse filename containing whitespace |
7506 | | // TODO(syoyo): Support parsing texture option flag after the filename. |
7507 | 299k | texture_name = std::string(token); |
7508 | 299k | token += texture_name.length(); |
7509 | 299k | #endif |
7510 | | |
7511 | 299k | found_texname = true; |
7512 | 299k | } |
7513 | 434k | } |
7514 | | |
7515 | 370k | if (found_texname) { |
7516 | 299k | (*texname) = texture_name; |
7517 | 299k | return true; |
7518 | 299k | } else { |
7519 | 71.6k | return false; |
7520 | 71.6k | } |
7521 | 370k | } |
7522 | | |
7523 | 6.40M | static void InitTexOpt(texture_option_t *texopt, const bool is_bump) { |
7524 | 6.40M | if (is_bump) { |
7525 | 492k | texopt->imfchan = 'l'; |
7526 | 5.91M | } else { |
7527 | 5.91M | texopt->imfchan = 'm'; |
7528 | 5.91M | } |
7529 | 6.40M | texopt->bump_multiplier = static_cast<real_t>(1.0); |
7530 | 6.40M | texopt->clamp = false; |
7531 | 6.40M | texopt->blendu = true; |
7532 | 6.40M | texopt->blendv = true; |
7533 | 6.40M | texopt->sharpness = static_cast<real_t>(1.0); |
7534 | 6.40M | texopt->brightness = static_cast<real_t>(0.0); |
7535 | 6.40M | texopt->contrast = static_cast<real_t>(1.0); |
7536 | 6.40M | texopt->origin_offset[0] = static_cast<real_t>(0.0); |
7537 | 6.40M | texopt->origin_offset[1] = static_cast<real_t>(0.0); |
7538 | 6.40M | texopt->origin_offset[2] = static_cast<real_t>(0.0); |
7539 | 6.40M | texopt->scale[0] = static_cast<real_t>(1.0); |
7540 | 6.40M | texopt->scale[1] = static_cast<real_t>(1.0); |
7541 | 6.40M | texopt->scale[2] = static_cast<real_t>(1.0); |
7542 | 6.40M | texopt->turbulence[0] = static_cast<real_t>(0.0); |
7543 | 6.40M | texopt->turbulence[1] = static_cast<real_t>(0.0); |
7544 | 6.40M | texopt->turbulence[2] = static_cast<real_t>(0.0); |
7545 | 6.40M | texopt->texture_resolution = -1; |
7546 | 6.40M | texopt->type = TEXTURE_TYPE_NONE; |
7547 | 6.40M | } |
7548 | | |
7549 | 492k | static void InitMaterial(material_t *material) { |
7550 | 492k | InitTexOpt(&material->ambient_texopt, /* is_bump */ false); |
7551 | 492k | InitTexOpt(&material->diffuse_texopt, /* is_bump */ false); |
7552 | 492k | InitTexOpt(&material->specular_texopt, /* is_bump */ false); |
7553 | 492k | InitTexOpt(&material->specular_highlight_texopt, /* is_bump */ false); |
7554 | 492k | InitTexOpt(&material->bump_texopt, /* is_bump */ true); |
7555 | 492k | InitTexOpt(&material->displacement_texopt, /* is_bump */ false); |
7556 | 492k | InitTexOpt(&material->alpha_texopt, /* is_bump */ false); |
7557 | 492k | InitTexOpt(&material->reflection_texopt, /* is_bump */ false); |
7558 | 492k | InitTexOpt(&material->roughness_texopt, /* is_bump */ false); |
7559 | 492k | InitTexOpt(&material->metallic_texopt, /* is_bump */ false); |
7560 | 492k | InitTexOpt(&material->sheen_texopt, /* is_bump */ false); |
7561 | 492k | InitTexOpt(&material->emissive_texopt, /* is_bump */ false); |
7562 | 492k | InitTexOpt(&material->normal_texopt, |
7563 | 492k | /* is_bump */ false); // @fixme { is_bump will be true? } |
7564 | 492k | material->name = ""; |
7565 | 492k | material->ambient_texname = ""; |
7566 | 492k | material->diffuse_texname = ""; |
7567 | 492k | material->specular_texname = ""; |
7568 | 492k | material->specular_highlight_texname = ""; |
7569 | 492k | material->bump_texname = ""; |
7570 | 492k | material->displacement_texname = ""; |
7571 | 492k | material->reflection_texname = ""; |
7572 | 492k | material->alpha_texname = ""; |
7573 | 1.97M | for (int i = 0; i < 3; i++) { |
7574 | 1.47M | material->ambient[i] = static_cast<real_t>(0.0); |
7575 | 1.47M | material->diffuse[i] = static_cast<real_t>(0.0); |
7576 | 1.47M | material->specular[i] = static_cast<real_t>(0.0); |
7577 | 1.47M | material->transmittance[i] = static_cast<real_t>(0.0); |
7578 | 1.47M | material->emission[i] = static_cast<real_t>(0.0); |
7579 | 1.47M | } |
7580 | 492k | material->illum = 0; |
7581 | 492k | material->dissolve = static_cast<real_t>(1.0); |
7582 | 492k | material->shininess = static_cast<real_t>(1.0); |
7583 | 492k | material->ior = static_cast<real_t>(1.0); |
7584 | | |
7585 | 492k | material->roughness = static_cast<real_t>(0.0); |
7586 | 492k | material->metallic = static_cast<real_t>(0.0); |
7587 | 492k | material->sheen = static_cast<real_t>(0.0); |
7588 | 492k | material->clearcoat_thickness = static_cast<real_t>(0.0); |
7589 | 492k | material->clearcoat_roughness = static_cast<real_t>(0.0); |
7590 | 492k | material->anisotropy_rotation = static_cast<real_t>(0.0); |
7591 | 492k | material->anisotropy = static_cast<real_t>(0.0); |
7592 | 492k | material->roughness_texname = ""; |
7593 | 492k | material->metallic_texname = ""; |
7594 | 492k | material->sheen_texname = ""; |
7595 | 492k | material->emissive_texname = ""; |
7596 | 492k | material->normal_texname = ""; |
7597 | | |
7598 | 492k | material->unknown_parameter.clear(); |
7599 | 492k | } |
7600 | | |
7601 | | // code from https://wrf.ecse.rpi.edu//Research/Short_Notes/pnpoly.html |
7602 | | template <typename T> |
7603 | 176M | static int pnpoly(int nvert, T *vertx, T *verty, T testx, T testy) { |
7604 | 176M | int i, j, c = 0; |
7605 | 707M | for (i = 0, j = nvert - 1; i < nvert; j = i++) { |
7606 | 530M | if (((verty[i] > testy) != (verty[j] > testy)) && |
7607 | 42.9k | (testx < |
7608 | 42.9k | (vertx[j] - vertx[i]) * (testy - verty[i]) / (verty[j] - verty[i]) + |
7609 | 42.9k | vertx[i])) |
7610 | 8.32k | c = !c; |
7611 | 530M | } |
7612 | 176M | return c; |
7613 | 176M | } |
7614 | | |
7615 | | struct TinyObjPoint { |
7616 | | real_t x, y, z; |
7617 | 0 | TinyObjPoint() : x(0), y(0), z(0) {} |
7618 | 0 | TinyObjPoint(real_t x_, real_t y_, real_t z_) : x(x_), y(y_), z(z_) {} |
7619 | | }; |
7620 | | |
7621 | 0 | inline TinyObjPoint cross(const TinyObjPoint &v1, const TinyObjPoint &v2) { |
7622 | 0 | return TinyObjPoint(v1.y * v2.z - v1.z * v2.y, v1.z * v2.x - v1.x * v2.z, |
7623 | 0 | v1.x * v2.y - v1.y * v2.x); |
7624 | 0 | } |
7625 | | |
7626 | 0 | inline real_t dot(const TinyObjPoint &v1, const TinyObjPoint &v2) { |
7627 | 0 | return (v1.x * v2.x + v1.y * v2.y + v1.z * v2.z); |
7628 | 0 | } |
7629 | | |
7630 | 0 | inline real_t GetLength(TinyObjPoint &e) { |
7631 | 0 | return std::sqrt(e.x * e.x + e.y * e.y + e.z * e.z); |
7632 | 0 | } |
7633 | | |
7634 | 0 | inline TinyObjPoint Normalize(TinyObjPoint e) { |
7635 | 0 | real_t len = GetLength(e); |
7636 | 0 | if (len <= real_t(0)) return TinyObjPoint(real_t(0), real_t(0), real_t(0)); |
7637 | 0 | real_t inv_length = real_t(1) / len; |
7638 | 0 | return TinyObjPoint(e.x * inv_length, e.y * inv_length, e.z * inv_length); |
7639 | 0 | } |
7640 | | |
7641 | | inline TinyObjPoint WorldToLocal(const TinyObjPoint &a, const TinyObjPoint &u, |
7642 | 0 | const TinyObjPoint &v, const TinyObjPoint &w) { |
7643 | 0 | return TinyObjPoint(dot(a, u), dot(a, v), dot(a, w)); |
7644 | 0 | } |
7645 | | |
7646 | | // TODO(syoyo): refactor function. |
7647 | | static bool exportGroupsToShape(shape_t *shape, const PrimGroup &prim_group, |
7648 | | const std::vector<tag_t> &tags, |
7649 | | const int material_id, const std::string &name, |
7650 | | bool triangulate, const std::vector<real_t> &v, |
7651 | 484k | std::string *warn) { |
7652 | 484k | if (prim_group.IsEmpty()) { |
7653 | 243k | return false; |
7654 | 243k | } |
7655 | | |
7656 | 241k | shape->name = name; |
7657 | | |
7658 | | // polygon |
7659 | 241k | if (!prim_group.faceGroup.empty()) { |
7660 | | // Flatten vertices and indices |
7661 | 350k | for (size_t i = 0; i < prim_group.faceGroup.size(); i++) { |
7662 | 312k | const face_t &face = prim_group.faceGroup[i]; |
7663 | | |
7664 | 312k | size_t npolys = face.vertex_indices.size(); |
7665 | | |
7666 | 312k | if (npolys < 3) { |
7667 | | // Face must have 3+ vertices. |
7668 | 257k | if (warn) { |
7669 | 257k | (*warn) += "Degenerated face found\n."; |
7670 | 257k | } |
7671 | 257k | continue; |
7672 | 257k | } |
7673 | | |
7674 | 54.6k | if (triangulate && npolys != 3) { |
7675 | 43.0k | if (npolys == 4) { |
7676 | 22.4k | vertex_index_t i0 = face.vertex_indices[0]; |
7677 | 22.4k | vertex_index_t i1 = face.vertex_indices[1]; |
7678 | 22.4k | vertex_index_t i2 = face.vertex_indices[2]; |
7679 | 22.4k | vertex_index_t i3 = face.vertex_indices[3]; |
7680 | | |
7681 | 22.4k | if (i0.v_idx < 0 || i1.v_idx < 0 || i2.v_idx < 0 || i3.v_idx < 0) { |
7682 | 0 | if (warn) { |
7683 | 0 | (*warn) += "Face with invalid vertex index found.\n"; |
7684 | 0 | } |
7685 | 0 | continue; |
7686 | 0 | } |
7687 | | |
7688 | 22.4k | size_t vi0 = size_t(i0.v_idx); |
7689 | 22.4k | size_t vi1 = size_t(i1.v_idx); |
7690 | 22.4k | size_t vi2 = size_t(i2.v_idx); |
7691 | 22.4k | size_t vi3 = size_t(i3.v_idx); |
7692 | | |
7693 | 22.4k | if (((3 * vi0 + 2) >= v.size()) || ((3 * vi1 + 2) >= v.size()) || |
7694 | 18.0k | ((3 * vi2 + 2) >= v.size()) || ((3 * vi3 + 2) >= v.size())) { |
7695 | | // Invalid triangle. |
7696 | | // FIXME(syoyo): Is it ok to simply skip this invalid triangle? |
7697 | 6.05k | if (warn) { |
7698 | 6.05k | (*warn) += "Face with invalid vertex index found.\n"; |
7699 | 6.05k | } |
7700 | 6.05k | continue; |
7701 | 6.05k | } |
7702 | | |
7703 | 16.4k | real_t v0x = v[vi0 * 3 + 0]; |
7704 | 16.4k | real_t v0y = v[vi0 * 3 + 1]; |
7705 | 16.4k | real_t v0z = v[vi0 * 3 + 2]; |
7706 | 16.4k | real_t v1x = v[vi1 * 3 + 0]; |
7707 | 16.4k | real_t v1y = v[vi1 * 3 + 1]; |
7708 | 16.4k | real_t v1z = v[vi1 * 3 + 2]; |
7709 | 16.4k | real_t v2x = v[vi2 * 3 + 0]; |
7710 | 16.4k | real_t v2y = v[vi2 * 3 + 1]; |
7711 | 16.4k | real_t v2z = v[vi2 * 3 + 2]; |
7712 | 16.4k | real_t v3x = v[vi3 * 3 + 0]; |
7713 | 16.4k | real_t v3y = v[vi3 * 3 + 1]; |
7714 | 16.4k | real_t v3z = v[vi3 * 3 + 2]; |
7715 | | |
7716 | | // There are two candidates to split the quad into two triangles. |
7717 | | // |
7718 | | // Choose the shortest edge. |
7719 | | // TODO: Is it better to determine the edge to split by calculating |
7720 | | // the area of each triangle? |
7721 | | // |
7722 | | // +---+ |
7723 | | // |\ | |
7724 | | // | \ | |
7725 | | // | \| |
7726 | | // +---+ |
7727 | | // |
7728 | | // +---+ |
7729 | | // | /| |
7730 | | // | / | |
7731 | | // |/ | |
7732 | | // +---+ |
7733 | | |
7734 | 16.4k | real_t e02x = v2x - v0x; |
7735 | 16.4k | real_t e02y = v2y - v0y; |
7736 | 16.4k | real_t e02z = v2z - v0z; |
7737 | 16.4k | real_t e13x = v3x - v1x; |
7738 | 16.4k | real_t e13y = v3y - v1y; |
7739 | 16.4k | real_t e13z = v3z - v1z; |
7740 | | |
7741 | 16.4k | real_t sqr02 = e02x * e02x + e02y * e02y + e02z * e02z; |
7742 | 16.4k | real_t sqr13 = e13x * e13x + e13y * e13y + e13z * e13z; |
7743 | | |
7744 | 16.4k | index_t idx0, idx1, idx2, idx3; |
7745 | | |
7746 | 16.4k | idx0.vertex_index = i0.v_idx; |
7747 | 16.4k | idx0.normal_index = i0.vn_idx; |
7748 | 16.4k | idx0.texcoord_index = i0.vt_idx; |
7749 | 16.4k | idx1.vertex_index = i1.v_idx; |
7750 | 16.4k | idx1.normal_index = i1.vn_idx; |
7751 | 16.4k | idx1.texcoord_index = i1.vt_idx; |
7752 | 16.4k | idx2.vertex_index = i2.v_idx; |
7753 | 16.4k | idx2.normal_index = i2.vn_idx; |
7754 | 16.4k | idx2.texcoord_index = i2.vt_idx; |
7755 | 16.4k | idx3.vertex_index = i3.v_idx; |
7756 | 16.4k | idx3.normal_index = i3.vn_idx; |
7757 | 16.4k | idx3.texcoord_index = i3.vt_idx; |
7758 | | |
7759 | 16.4k | if (sqr02 < sqr13) { |
7760 | | // [0, 1, 2], [0, 2, 3] |
7761 | 2.01k | shape->mesh.indices.push_back(idx0); |
7762 | 2.01k | shape->mesh.indices.push_back(idx1); |
7763 | 2.01k | shape->mesh.indices.push_back(idx2); |
7764 | | |
7765 | 2.01k | shape->mesh.indices.push_back(idx0); |
7766 | 2.01k | shape->mesh.indices.push_back(idx2); |
7767 | 2.01k | shape->mesh.indices.push_back(idx3); |
7768 | 14.4k | } else { |
7769 | | // [0, 1, 3], [1, 2, 3] |
7770 | 14.4k | shape->mesh.indices.push_back(idx0); |
7771 | 14.4k | shape->mesh.indices.push_back(idx1); |
7772 | 14.4k | shape->mesh.indices.push_back(idx3); |
7773 | | |
7774 | 14.4k | shape->mesh.indices.push_back(idx1); |
7775 | 14.4k | shape->mesh.indices.push_back(idx2); |
7776 | 14.4k | shape->mesh.indices.push_back(idx3); |
7777 | 14.4k | } |
7778 | | |
7779 | | // Two triangle faces |
7780 | 16.4k | shape->mesh.num_face_vertices.push_back(3); |
7781 | 16.4k | shape->mesh.num_face_vertices.push_back(3); |
7782 | | |
7783 | 16.4k | shape->mesh.material_ids.push_back(material_id); |
7784 | 16.4k | shape->mesh.material_ids.push_back(material_id); |
7785 | | |
7786 | 16.4k | shape->mesh.smoothing_group_ids.push_back(face.smoothing_group_id); |
7787 | 16.4k | shape->mesh.smoothing_group_ids.push_back(face.smoothing_group_id); |
7788 | | |
7789 | 20.5k | } else { |
7790 | | #ifdef TINYOBJLOADER_USE_MAPBOX_EARCUT |
7791 | | // Validate all vertex indices before accessing the vertex array. |
7792 | | { |
7793 | | bool valid_poly = true; |
7794 | | for (size_t k = 0; k < npolys; ++k) { |
7795 | | size_t vi = size_t(face.vertex_indices[k].v_idx); |
7796 | | if ((3 * vi + 2) >= v.size()) { |
7797 | | valid_poly = false; |
7798 | | break; |
7799 | | } |
7800 | | } |
7801 | | if (!valid_poly) { |
7802 | | if (warn) { |
7803 | | (*warn) += "Face with invalid vertex index found.\n"; |
7804 | | } |
7805 | | continue; |
7806 | | } |
7807 | | } |
7808 | | |
7809 | | vertex_index_t i0 = face.vertex_indices[0]; |
7810 | | vertex_index_t i0_2 = i0; |
7811 | | |
7812 | | // TMW change: Find the normal axis of the polygon using Newell's |
7813 | | // method |
7814 | | TinyObjPoint n; |
7815 | | for (size_t k = 0; k < npolys; ++k) { |
7816 | | i0 = face.vertex_indices[k % npolys]; |
7817 | | size_t vi0 = size_t(i0.v_idx); |
7818 | | |
7819 | | size_t j = (k + 1) % npolys; |
7820 | | i0_2 = face.vertex_indices[j]; |
7821 | | size_t vi0_2 = size_t(i0_2.v_idx); |
7822 | | |
7823 | | real_t v0x = v[vi0 * 3 + 0]; |
7824 | | real_t v0y = v[vi0 * 3 + 1]; |
7825 | | real_t v0z = v[vi0 * 3 + 2]; |
7826 | | |
7827 | | real_t v0x_2 = v[vi0_2 * 3 + 0]; |
7828 | | real_t v0y_2 = v[vi0_2 * 3 + 1]; |
7829 | | real_t v0z_2 = v[vi0_2 * 3 + 2]; |
7830 | | |
7831 | | const TinyObjPoint point1(v0x, v0y, v0z); |
7832 | | const TinyObjPoint point2(v0x_2, v0y_2, v0z_2); |
7833 | | |
7834 | | TinyObjPoint a(point1.x - point2.x, point1.y - point2.y, |
7835 | | point1.z - point2.z); |
7836 | | TinyObjPoint b(point1.x + point2.x, point1.y + point2.y, |
7837 | | point1.z + point2.z); |
7838 | | |
7839 | | n.x += (a.y * b.z); |
7840 | | n.y += (a.z * b.x); |
7841 | | n.z += (a.x * b.y); |
7842 | | } |
7843 | | real_t length_n = GetLength(n); |
7844 | | // Check if zero length normal |
7845 | | if (length_n <= 0) { |
7846 | | continue; |
7847 | | } |
7848 | | // Negative is to flip the normal to the correct direction |
7849 | | real_t inv_length = -real_t(1.0) / length_n; |
7850 | | n.x *= inv_length; |
7851 | | n.y *= inv_length; |
7852 | | n.z *= inv_length; |
7853 | | |
7854 | | TinyObjPoint axis_w, axis_v, axis_u; |
7855 | | axis_w = n; |
7856 | | TinyObjPoint a; |
7857 | | if (std::fabs(axis_w.x) > real_t(0.9999999)) { |
7858 | | a = TinyObjPoint(0, 1, 0); |
7859 | | } else { |
7860 | | a = TinyObjPoint(1, 0, 0); |
7861 | | } |
7862 | | axis_v = Normalize(cross(axis_w, a)); |
7863 | | axis_u = cross(axis_w, axis_v); |
7864 | | using Point = std::array<real_t, 2>; |
7865 | | |
7866 | | // first polyline define the main polygon. |
7867 | | // following polylines define holes(not used in tinyobj). |
7868 | | std::vector<std::vector<Point> > polygon; |
7869 | | |
7870 | | std::vector<Point> polyline; |
7871 | | |
7872 | | // TMW change: Find best normal and project v0x and v0y to those |
7873 | | // coordinates, instead of picking a plane aligned with an axis (which |
7874 | | // can flip polygons). |
7875 | | |
7876 | | // Fill polygon data(facevarying vertices). |
7877 | | for (size_t k = 0; k < npolys; k++) { |
7878 | | i0 = face.vertex_indices[k]; |
7879 | | size_t vi0 = size_t(i0.v_idx); |
7880 | | |
7881 | | assert(((3 * vi0 + 2) < v.size())); |
7882 | | |
7883 | | real_t v0x = v[vi0 * 3 + 0]; |
7884 | | real_t v0y = v[vi0 * 3 + 1]; |
7885 | | real_t v0z = v[vi0 * 3 + 2]; |
7886 | | |
7887 | | TinyObjPoint polypoint(v0x, v0y, v0z); |
7888 | | TinyObjPoint loc = WorldToLocal(polypoint, axis_u, axis_v, axis_w); |
7889 | | |
7890 | | polyline.push_back({loc.x, loc.y}); |
7891 | | } |
7892 | | |
7893 | | polygon.push_back(polyline); |
7894 | | std::vector<uint32_t> indices = mapbox::earcut<uint32_t>(polygon); |
7895 | | // => result = 3 * faces, clockwise |
7896 | | |
7897 | | assert(indices.size() % 3 == 0); |
7898 | | |
7899 | | // Reconstruct vertex_index_t |
7900 | | for (size_t k = 0; k < indices.size() / 3; k++) { |
7901 | | { |
7902 | | index_t idx0, idx1, idx2; |
7903 | | idx0.vertex_index = face.vertex_indices[indices[3 * k + 0]].v_idx; |
7904 | | idx0.normal_index = |
7905 | | face.vertex_indices[indices[3 * k + 0]].vn_idx; |
7906 | | idx0.texcoord_index = |
7907 | | face.vertex_indices[indices[3 * k + 0]].vt_idx; |
7908 | | idx1.vertex_index = face.vertex_indices[indices[3 * k + 1]].v_idx; |
7909 | | idx1.normal_index = |
7910 | | face.vertex_indices[indices[3 * k + 1]].vn_idx; |
7911 | | idx1.texcoord_index = |
7912 | | face.vertex_indices[indices[3 * k + 1]].vt_idx; |
7913 | | idx2.vertex_index = face.vertex_indices[indices[3 * k + 2]].v_idx; |
7914 | | idx2.normal_index = |
7915 | | face.vertex_indices[indices[3 * k + 2]].vn_idx; |
7916 | | idx2.texcoord_index = |
7917 | | face.vertex_indices[indices[3 * k + 2]].vt_idx; |
7918 | | |
7919 | | shape->mesh.indices.push_back(idx0); |
7920 | | shape->mesh.indices.push_back(idx1); |
7921 | | shape->mesh.indices.push_back(idx2); |
7922 | | |
7923 | | shape->mesh.num_face_vertices.push_back(3); |
7924 | | shape->mesh.material_ids.push_back(material_id); |
7925 | | shape->mesh.smoothing_group_ids.push_back( |
7926 | | face.smoothing_group_id); |
7927 | | } |
7928 | | } |
7929 | | |
7930 | | #else // Built-in ear clipping triangulation |
7931 | 20.5k | vertex_index_t i0 = face.vertex_indices[0]; |
7932 | 20.5k | vertex_index_t i1(-1); |
7933 | 20.5k | vertex_index_t i2 = face.vertex_indices[1]; |
7934 | | |
7935 | | // find the two axes to work in |
7936 | 20.5k | size_t axes[2] = {1, 2}; |
7937 | 1.92M | for (size_t k = 0; k < npolys; ++k) { |
7938 | 1.90M | i0 = face.vertex_indices[(k + 0) % npolys]; |
7939 | 1.90M | i1 = face.vertex_indices[(k + 1) % npolys]; |
7940 | 1.90M | i2 = face.vertex_indices[(k + 2) % npolys]; |
7941 | 1.90M | size_t vi0 = size_t(i0.v_idx); |
7942 | 1.90M | size_t vi1 = size_t(i1.v_idx); |
7943 | 1.90M | size_t vi2 = size_t(i2.v_idx); |
7944 | | |
7945 | 1.90M | if (((3 * vi0 + 2) >= v.size()) || ((3 * vi1 + 2) >= v.size()) || |
7946 | 1.84M | ((3 * vi2 + 2) >= v.size())) { |
7947 | | // Invalid triangle. |
7948 | | // FIXME(syoyo): Is it ok to simply skip this invalid triangle? |
7949 | 1.84M | continue; |
7950 | 1.84M | } |
7951 | 63.6k | real_t v0x = v[vi0 * 3 + 0]; |
7952 | 63.6k | real_t v0y = v[vi0 * 3 + 1]; |
7953 | 63.6k | real_t v0z = v[vi0 * 3 + 2]; |
7954 | 63.6k | real_t v1x = v[vi1 * 3 + 0]; |
7955 | 63.6k | real_t v1y = v[vi1 * 3 + 1]; |
7956 | 63.6k | real_t v1z = v[vi1 * 3 + 2]; |
7957 | 63.6k | real_t v2x = v[vi2 * 3 + 0]; |
7958 | 63.6k | real_t v2y = v[vi2 * 3 + 1]; |
7959 | 63.6k | real_t v2z = v[vi2 * 3 + 2]; |
7960 | 63.6k | real_t e0x = v1x - v0x; |
7961 | 63.6k | real_t e0y = v1y - v0y; |
7962 | 63.6k | real_t e0z = v1z - v0z; |
7963 | 63.6k | real_t e1x = v2x - v1x; |
7964 | 63.6k | real_t e1y = v2y - v1y; |
7965 | 63.6k | real_t e1z = v2z - v1z; |
7966 | 63.6k | real_t cx = std::fabs(e0y * e1z - e0z * e1y); |
7967 | 63.6k | real_t cy = std::fabs(e0z * e1x - e0x * e1z); |
7968 | 63.6k | real_t cz = std::fabs(e0x * e1y - e0y * e1x); |
7969 | 63.6k | const real_t epsilon = std::numeric_limits<real_t>::epsilon(); |
7970 | | // std::cout << "cx " << cx << ", cy " << cy << ", cz " << cz << |
7971 | | // "\n"; |
7972 | 63.6k | if (cx > epsilon || cy > epsilon || cz > epsilon) { |
7973 | | // std::cout << "corner\n"; |
7974 | | // found a corner |
7975 | 4.31k | if (cx > cy && cx > cz) { |
7976 | | // std::cout << "pattern0\n"; |
7977 | 2.85k | } else { |
7978 | | // std::cout << "axes[0] = 0\n"; |
7979 | 2.85k | axes[0] = 0; |
7980 | 2.85k | if (cz > cx && cz > cy) { |
7981 | | // std::cout << "axes[1] = 1\n"; |
7982 | 1.64k | axes[1] = 1; |
7983 | 1.64k | } |
7984 | 2.85k | } |
7985 | 4.31k | break; |
7986 | 4.31k | } |
7987 | 63.6k | } |
7988 | | |
7989 | 20.5k | face_t remainingFace = face; // copy |
7990 | 20.5k | size_t guess_vert = 0; |
7991 | 20.5k | vertex_index_t ind[3]; |
7992 | 20.5k | real_t vx[3]; |
7993 | 20.5k | real_t vy[3]; |
7994 | | |
7995 | | // How many iterations can we do without decreasing the remaining |
7996 | | // vertices. |
7997 | 20.5k | size_t remainingIterations = face.vertex_indices.size(); |
7998 | 20.5k | size_t previousRemainingVertices = |
7999 | 20.5k | remainingFace.vertex_indices.size(); |
8000 | | |
8001 | 1.88M | while (remainingFace.vertex_indices.size() > 3 && |
8002 | 1.86M | remainingIterations > 0) { |
8003 | | // std::cout << "remainingIterations " << remainingIterations << |
8004 | | // "\n"; |
8005 | | |
8006 | 1.86M | npolys = remainingFace.vertex_indices.size(); |
8007 | 1.86M | if (guess_vert >= npolys) { |
8008 | 534 | guess_vert -= npolys; |
8009 | 534 | } |
8010 | | |
8011 | 1.86M | if (previousRemainingVertices != npolys) { |
8012 | | // The number of remaining vertices decreased. Reset counters. |
8013 | 1.83M | previousRemainingVertices = npolys; |
8014 | 1.83M | remainingIterations = npolys; |
8015 | 1.83M | } else { |
8016 | | // We didn't consume a vertex on previous iteration, reduce the |
8017 | | // available iterations. |
8018 | 26.9k | remainingIterations--; |
8019 | 26.9k | } |
8020 | | |
8021 | 7.46M | for (size_t k = 0; k < 3; k++) { |
8022 | 5.59M | ind[k] = remainingFace.vertex_indices[(guess_vert + k) % npolys]; |
8023 | 5.59M | size_t vi = size_t(ind[k].v_idx); |
8024 | 5.59M | if (((vi * 3 + axes[0]) >= v.size()) || |
8025 | 5.37M | ((vi * 3 + axes[1]) >= v.size())) { |
8026 | | // ??? |
8027 | 5.37M | vx[k] = static_cast<real_t>(0.0); |
8028 | 5.37M | vy[k] = static_cast<real_t>(0.0); |
8029 | 5.37M | } else { |
8030 | 221k | vx[k] = v[vi * 3 + axes[0]]; |
8031 | 221k | vy[k] = v[vi * 3 + axes[1]]; |
8032 | 221k | } |
8033 | 5.59M | } |
8034 | | |
8035 | | // |
8036 | | // area is calculated per face |
8037 | | // |
8038 | 1.86M | real_t e0x = vx[1] - vx[0]; |
8039 | 1.86M | real_t e0y = vy[1] - vy[0]; |
8040 | 1.86M | real_t e1x = vx[2] - vx[1]; |
8041 | 1.86M | real_t e1y = vy[2] - vy[1]; |
8042 | 1.86M | real_t cross = e0x * e1y - e0y * e1x; |
8043 | | // std::cout << "axes = " << axes[0] << ", " << axes[1] << "\n"; |
8044 | | // std::cout << "e0x, e0y, e1x, e1y " << e0x << ", " << e0y << ", " |
8045 | | // << e1x << ", " << e1y << "\n"; |
8046 | | |
8047 | 1.86M | real_t area = |
8048 | 1.86M | (vx[0] * vy[1] - vy[0] * vx[1]) * static_cast<real_t>(0.5); |
8049 | | // std::cout << "cross " << cross << ", area " << area << "\n"; |
8050 | | // if an internal angle |
8051 | 1.86M | if (cross * area < static_cast<real_t>(0.0)) { |
8052 | | // std::cout << "internal \n"; |
8053 | 3.42k | guess_vert += 1; |
8054 | | // std::cout << "guess vert : " << guess_vert << "\n"; |
8055 | 3.42k | continue; |
8056 | 3.42k | } |
8057 | | |
8058 | | // check all other verts in case they are inside this triangle |
8059 | 1.86M | bool overlap = false; |
8060 | 3.16G | for (size_t otherVert = 3; otherVert < npolys; ++otherVert) { |
8061 | 3.16G | size_t idx = (guess_vert + otherVert) % npolys; |
8062 | | |
8063 | 3.16G | if (idx >= remainingFace.vertex_indices.size()) { |
8064 | | // std::cout << "???0\n"; |
8065 | | // ??? |
8066 | 0 | continue; |
8067 | 0 | } |
8068 | | |
8069 | 3.16G | size_t ovi = size_t(remainingFace.vertex_indices[idx].v_idx); |
8070 | | |
8071 | 3.16G | if (((ovi * 3 + axes[0]) >= v.size()) || |
8072 | 2.98G | ((ovi * 3 + axes[1]) >= v.size())) { |
8073 | | // std::cout << "???1\n"; |
8074 | | // ??? |
8075 | 2.98G | continue; |
8076 | 2.98G | } |
8077 | 176M | real_t tx = v[ovi * 3 + axes[0]]; |
8078 | 176M | real_t ty = v[ovi * 3 + axes[1]]; |
8079 | 176M | if (pnpoly(3, vx, vy, tx, ty)) { |
8080 | | // std::cout << "overlap\n"; |
8081 | 3.31k | overlap = true; |
8082 | 3.31k | break; |
8083 | 3.31k | } |
8084 | 176M | } |
8085 | | |
8086 | 1.86M | if (overlap) { |
8087 | | // std::cout << "overlap2\n"; |
8088 | 3.31k | guess_vert += 1; |
8089 | 3.31k | continue; |
8090 | 3.31k | } |
8091 | | |
8092 | | // this triangle is an ear |
8093 | 1.85M | { |
8094 | 1.85M | index_t idx0, idx1, idx2; |
8095 | 1.85M | idx0.vertex_index = ind[0].v_idx; |
8096 | 1.85M | idx0.normal_index = ind[0].vn_idx; |
8097 | 1.85M | idx0.texcoord_index = ind[0].vt_idx; |
8098 | 1.85M | idx1.vertex_index = ind[1].v_idx; |
8099 | 1.85M | idx1.normal_index = ind[1].vn_idx; |
8100 | 1.85M | idx1.texcoord_index = ind[1].vt_idx; |
8101 | 1.85M | idx2.vertex_index = ind[2].v_idx; |
8102 | 1.85M | idx2.normal_index = ind[2].vn_idx; |
8103 | 1.85M | idx2.texcoord_index = ind[2].vt_idx; |
8104 | | |
8105 | 1.85M | shape->mesh.indices.push_back(idx0); |
8106 | 1.85M | shape->mesh.indices.push_back(idx1); |
8107 | 1.85M | shape->mesh.indices.push_back(idx2); |
8108 | | |
8109 | 1.85M | shape->mesh.num_face_vertices.push_back(3); |
8110 | 1.85M | shape->mesh.material_ids.push_back(material_id); |
8111 | 1.85M | shape->mesh.smoothing_group_ids.push_back( |
8112 | 1.85M | face.smoothing_group_id); |
8113 | 1.85M | } |
8114 | | |
8115 | | // remove v1 from the list |
8116 | 1.85M | size_t removed_vert_index = (guess_vert + 1) % npolys; |
8117 | 3.16G | while (removed_vert_index + 1 < npolys) { |
8118 | 3.16G | remainingFace.vertex_indices[removed_vert_index] = |
8119 | 3.16G | remainingFace.vertex_indices[removed_vert_index + 1]; |
8120 | 3.16G | removed_vert_index += 1; |
8121 | 3.16G | } |
8122 | 1.85M | remainingFace.vertex_indices.pop_back(); |
8123 | 1.85M | } |
8124 | | |
8125 | | // std::cout << "remainingFace.vi.size = " << |
8126 | | // remainingFace.vertex_indices.size() << "\n"; |
8127 | 20.5k | if (remainingFace.vertex_indices.size() == 3) { |
8128 | 20.1k | i0 = remainingFace.vertex_indices[0]; |
8129 | 20.1k | i1 = remainingFace.vertex_indices[1]; |
8130 | 20.1k | i2 = remainingFace.vertex_indices[2]; |
8131 | 20.1k | { |
8132 | 20.1k | index_t idx0, idx1, idx2; |
8133 | 20.1k | idx0.vertex_index = i0.v_idx; |
8134 | 20.1k | idx0.normal_index = i0.vn_idx; |
8135 | 20.1k | idx0.texcoord_index = i0.vt_idx; |
8136 | 20.1k | idx1.vertex_index = i1.v_idx; |
8137 | 20.1k | idx1.normal_index = i1.vn_idx; |
8138 | 20.1k | idx1.texcoord_index = i1.vt_idx; |
8139 | 20.1k | idx2.vertex_index = i2.v_idx; |
8140 | 20.1k | idx2.normal_index = i2.vn_idx; |
8141 | 20.1k | idx2.texcoord_index = i2.vt_idx; |
8142 | | |
8143 | 20.1k | shape->mesh.indices.push_back(idx0); |
8144 | 20.1k | shape->mesh.indices.push_back(idx1); |
8145 | 20.1k | shape->mesh.indices.push_back(idx2); |
8146 | | |
8147 | 20.1k | shape->mesh.num_face_vertices.push_back(3); |
8148 | 20.1k | shape->mesh.material_ids.push_back(material_id); |
8149 | 20.1k | shape->mesh.smoothing_group_ids.push_back( |
8150 | 20.1k | face.smoothing_group_id); |
8151 | 20.1k | } |
8152 | 20.1k | } |
8153 | 20.5k | #endif |
8154 | 20.5k | } // npolys |
8155 | 43.0k | } else { |
8156 | 46.5k | for (size_t k = 0; k < npolys; k++) { |
8157 | 34.9k | index_t idx; |
8158 | 34.9k | idx.vertex_index = face.vertex_indices[k].v_idx; |
8159 | 34.9k | idx.normal_index = face.vertex_indices[k].vn_idx; |
8160 | 34.9k | idx.texcoord_index = face.vertex_indices[k].vt_idx; |
8161 | 34.9k | shape->mesh.indices.push_back(idx); |
8162 | 34.9k | } |
8163 | | |
8164 | 11.6k | shape->mesh.num_face_vertices.push_back( |
8165 | 11.6k | static_cast<unsigned int>(npolys)); |
8166 | 11.6k | shape->mesh.material_ids.push_back(material_id); // per face |
8167 | 11.6k | shape->mesh.smoothing_group_ids.push_back( |
8168 | 11.6k | face.smoothing_group_id); // per face |
8169 | 11.6k | } |
8170 | 54.6k | } |
8171 | | |
8172 | 37.8k | shape->mesh.tags = tags; |
8173 | 37.8k | } |
8174 | | |
8175 | | // line |
8176 | 241k | if (!prim_group.lineGroup.empty()) { |
8177 | | // Flatten indices |
8178 | 213M | for (size_t i = 0; i < prim_group.lineGroup.size(); i++) { |
8179 | 244M | for (size_t j = 0; j < prim_group.lineGroup[i].vertex_indices.size(); |
8180 | 213M | j++) { |
8181 | 30.5M | const vertex_index_t &vi = prim_group.lineGroup[i].vertex_indices[j]; |
8182 | | |
8183 | 30.5M | index_t idx; |
8184 | 30.5M | idx.vertex_index = vi.v_idx; |
8185 | 30.5M | idx.normal_index = vi.vn_idx; |
8186 | 30.5M | idx.texcoord_index = vi.vt_idx; |
8187 | | |
8188 | 30.5M | shape->lines.indices.push_back(idx); |
8189 | 30.5M | } |
8190 | | |
8191 | 213M | shape->lines.num_line_vertices.push_back( |
8192 | 213M | int(prim_group.lineGroup[i].vertex_indices.size())); |
8193 | 213M | } |
8194 | 35.9k | } |
8195 | | |
8196 | | // points |
8197 | 241k | if (!prim_group.pointsGroup.empty()) { |
8198 | | // Flatten & convert indices |
8199 | 497k | for (size_t i = 0; i < prim_group.pointsGroup.size(); i++) { |
8200 | 3.18M | for (size_t j = 0; j < prim_group.pointsGroup[i].vertex_indices.size(); |
8201 | 2.87M | j++) { |
8202 | 2.87M | const vertex_index_t &vi = prim_group.pointsGroup[i].vertex_indices[j]; |
8203 | | |
8204 | 2.87M | index_t idx; |
8205 | 2.87M | idx.vertex_index = vi.v_idx; |
8206 | 2.87M | idx.normal_index = vi.vn_idx; |
8207 | 2.87M | idx.texcoord_index = vi.vt_idx; |
8208 | | |
8209 | 2.87M | shape->points.indices.push_back(idx); |
8210 | 2.87M | } |
8211 | 312k | } |
8212 | 184k | } |
8213 | | |
8214 | 241k | return true; |
8215 | 484k | } |
8216 | | |
8217 | | // Split a string with specified delimiter character and escape character. |
8218 | | // https://rosettacode.org/wiki/Tokenize_a_string_with_escaping#C.2B.2B |
8219 | | static void SplitString(const std::string &s, char delim, char escape, |
8220 | 61.0k | std::vector<std::string> &elems) { |
8221 | 61.0k | std::string token; |
8222 | | |
8223 | 61.0k | bool escaping = false; |
8224 | 17.3M | for (size_t i = 0; i < s.size(); ++i) { |
8225 | 17.3M | char ch = s[i]; |
8226 | 17.3M | if (escaping) { |
8227 | 19.9k | escaping = false; |
8228 | 17.2M | } else if (ch == escape) { |
8229 | 22.6k | if ((i + 1) < s.size()) { |
8230 | 21.9k | const char next = s[i + 1]; |
8231 | 21.9k | if ((next == delim) || (next == escape)) { |
8232 | 19.9k | escaping = true; |
8233 | 19.9k | continue; |
8234 | 19.9k | } |
8235 | 21.9k | } |
8236 | 17.2M | } else if (ch == delim) { |
8237 | 1.13M | if (!token.empty()) { |
8238 | 1.12M | elems.push_back(token); |
8239 | 1.12M | } |
8240 | 1.13M | token.clear(); |
8241 | 1.13M | continue; |
8242 | 1.13M | } |
8243 | 16.1M | token += ch; |
8244 | 16.1M | } |
8245 | | |
8246 | 61.0k | elems.push_back(token); |
8247 | 61.0k | } |
8248 | | |
8249 | 61.0k | static void RemoveEmptyTokens(std::vector<std::string> *tokens) { |
8250 | 61.0k | if (!tokens) return; |
8251 | | |
8252 | 61.0k | const std::vector<std::string> &src = *tokens; |
8253 | 61.0k | std::vector<std::string> filtered; |
8254 | 61.0k | filtered.reserve(src.size()); |
8255 | 1.24M | for (size_t i = 0; i < src.size(); i++) { |
8256 | 1.18M | if (!src[i].empty()) { |
8257 | 1.17M | filtered.push_back(src[i]); |
8258 | 1.17M | } |
8259 | 1.18M | } |
8260 | 61.0k | tokens->swap(filtered); |
8261 | 61.0k | } |
8262 | | |
8263 | | static std::string JoinPath(const std::string &dir, |
8264 | 0 | const std::string &filename) { |
8265 | 0 | if (dir.empty()) { |
8266 | 0 | return filename; |
8267 | 0 | } else { |
8268 | | // check '/' |
8269 | 0 | char lastChar = *dir.rbegin(); |
8270 | 0 | if (lastChar != '/') { |
8271 | 0 | return dir + std::string("/") + filename; |
8272 | 0 | } else { |
8273 | 0 | return dir + filename; |
8274 | 0 | } |
8275 | 0 | } |
8276 | 0 | } |
8277 | | |
8278 | | static bool LoadMtlInternal(std::map<std::string, int> *material_map, |
8279 | | std::vector<material_t> *materials, |
8280 | | StreamReader &sr, |
8281 | | std::string *warning, std::string *err, |
8282 | 11.8k | const std::string &filename = "<stream>") { |
8283 | 11.8k | if (sr.has_errors()) { |
8284 | 0 | if (err) { |
8285 | 0 | (*err) += sr.get_errors(); |
8286 | 0 | } |
8287 | 0 | return false; |
8288 | 0 | } |
8289 | | |
8290 | 11.8k | material_t material; |
8291 | 11.8k | InitMaterial(&material); |
8292 | | |
8293 | | // Issue 43. `d` wins against `Tr` since `Tr` is not in the MTL specification. |
8294 | 11.8k | bool has_d = false; |
8295 | 11.8k | bool has_tr = false; |
8296 | | |
8297 | | // has_kd is used to set a default diffuse value when map_Kd is present |
8298 | | // and Kd is not. |
8299 | 11.8k | bool has_kd = false; |
8300 | | |
8301 | 11.8k | std::stringstream warn_ss; |
8302 | | |
8303 | | // Handle BOM |
8304 | 11.8k | if (sr.remaining() >= 3 && |
8305 | 9.32k | static_cast<unsigned char>(sr.peek()) == 0xEF && |
8306 | 64 | static_cast<unsigned char>(sr.peek_at(1)) == 0xBB && |
8307 | 41 | static_cast<unsigned char>(sr.peek_at(2)) == 0xBF) { |
8308 | 13 | sr.advance(3); |
8309 | 13 | } |
8310 | | |
8311 | 3.11M | while (!sr.eof()) { |
8312 | 3.10M | sr.skip_space(); |
8313 | 3.10M | if (sr.at_line_end()) { sr.skip_line(); continue; } |
8314 | 2.71M | if (sr.peek() == '#') { sr.skip_line(); continue; } |
8315 | | |
8316 | 2.68M | size_t line_num = sr.line_num(); |
8317 | | |
8318 | | // new mtl |
8319 | 2.68M | if (sr.match("newmtl", 6) && (sr.peek_at(6) == ' ' || sr.peek_at(6) == '\t')) { |
8320 | | // flush previous material. |
8321 | 480k | if (!material.name.empty()) { |
8322 | 478k | material_map->insert(std::pair<std::string, int>( |
8323 | 478k | material.name, static_cast<int>(materials->size()))); |
8324 | 478k | materials->push_back(material); |
8325 | 478k | } |
8326 | | |
8327 | 480k | InitMaterial(&material); |
8328 | | |
8329 | 480k | has_d = false; |
8330 | 480k | has_tr = false; |
8331 | 480k | has_kd = false; |
8332 | | |
8333 | 480k | sr.advance(7); |
8334 | 480k | { |
8335 | 480k | std::string namebuf = sr_parseString(sr); |
8336 | 480k | if (namebuf.empty()) { |
8337 | 751 | if (warning) { |
8338 | 751 | (*warning) += "empty material name in `newmtl`\n"; |
8339 | 751 | } |
8340 | 751 | } |
8341 | 480k | material.name = namebuf; |
8342 | 480k | } |
8343 | 480k | sr.skip_line(); |
8344 | 480k | continue; |
8345 | 480k | } |
8346 | | |
8347 | | // ambient |
8348 | 2.20M | if (sr.peek() == 'K' && sr.peek_at(1) == 'a' && (sr.peek_at(2) == ' ' || sr.peek_at(2) == '\t')) { |
8349 | 19.6k | sr.advance(2); |
8350 | 19.6k | real_t r, g, b; |
8351 | 19.6k | if (!sr_parseReal3(&r, &g, &b, sr, err, filename)) return false; |
8352 | 19.6k | material.ambient[0] = r; |
8353 | 19.6k | material.ambient[1] = g; |
8354 | 19.6k | material.ambient[2] = b; |
8355 | 19.6k | sr.skip_line(); |
8356 | 19.6k | continue; |
8357 | 19.6k | } |
8358 | | |
8359 | | // diffuse |
8360 | 2.18M | if (sr.peek() == 'K' && sr.peek_at(1) == 'd' && (sr.peek_at(2) == ' ' || sr.peek_at(2) == '\t')) { |
8361 | 6.11k | sr.advance(2); |
8362 | 6.11k | real_t r, g, b; |
8363 | 6.11k | if (!sr_parseReal3(&r, &g, &b, sr, err, filename)) return false; |
8364 | 6.06k | material.diffuse[0] = r; |
8365 | 6.06k | material.diffuse[1] = g; |
8366 | 6.06k | material.diffuse[2] = b; |
8367 | 6.06k | has_kd = true; |
8368 | 6.06k | sr.skip_line(); |
8369 | 6.06k | continue; |
8370 | 6.11k | } |
8371 | | |
8372 | | // specular |
8373 | 2.18M | if (sr.peek() == 'K' && sr.peek_at(1) == 's' && (sr.peek_at(2) == ' ' || sr.peek_at(2) == '\t')) { |
8374 | 6.13k | sr.advance(2); |
8375 | 6.13k | real_t r, g, b; |
8376 | 6.13k | if (!sr_parseReal3(&r, &g, &b, sr, err, filename)) return false; |
8377 | 6.10k | material.specular[0] = r; |
8378 | 6.10k | material.specular[1] = g; |
8379 | 6.10k | material.specular[2] = b; |
8380 | 6.10k | sr.skip_line(); |
8381 | 6.10k | continue; |
8382 | 6.13k | } |
8383 | | |
8384 | | // transmittance |
8385 | 2.17M | if ((sr.peek() == 'K' && sr.peek_at(1) == 't' && (sr.peek_at(2) == ' ' || sr.peek_at(2) == '\t')) || |
8386 | 2.17M | (sr.peek() == 'T' && sr.peek_at(1) == 'f' && (sr.peek_at(2) == ' ' || sr.peek_at(2) == '\t'))) { |
8387 | 39.9k | sr.advance(2); |
8388 | 39.9k | real_t r, g, b; |
8389 | 39.9k | if (!sr_parseReal3(&r, &g, &b, sr, err, filename)) return false; |
8390 | 39.9k | material.transmittance[0] = r; |
8391 | 39.9k | material.transmittance[1] = g; |
8392 | 39.9k | material.transmittance[2] = b; |
8393 | 39.9k | sr.skip_line(); |
8394 | 39.9k | continue; |
8395 | 39.9k | } |
8396 | | |
8397 | | // ior(index of refraction) |
8398 | 2.13M | if (sr.peek() == 'N' && sr.peek_at(1) == 'i' && (sr.peek_at(2) == ' ' || sr.peek_at(2) == '\t')) { |
8399 | 14.4k | sr.advance(2); |
8400 | 14.4k | if (!sr_parseReal(sr, &material.ior, 0.0, err, filename)) return false; |
8401 | 14.4k | sr.skip_line(); |
8402 | 14.4k | continue; |
8403 | 14.4k | } |
8404 | | |
8405 | | // emission |
8406 | 2.12M | if (sr.peek() == 'K' && sr.peek_at(1) == 'e' && (sr.peek_at(2) == ' ' || sr.peek_at(2) == '\t')) { |
8407 | 25.7k | sr.advance(2); |
8408 | 25.7k | real_t r, g, b; |
8409 | 25.7k | if (!sr_parseReal3(&r, &g, &b, sr, err, filename)) return false; |
8410 | 25.7k | material.emission[0] = r; |
8411 | 25.7k | material.emission[1] = g; |
8412 | 25.7k | material.emission[2] = b; |
8413 | 25.7k | sr.skip_line(); |
8414 | 25.7k | continue; |
8415 | 25.7k | } |
8416 | | |
8417 | | // shininess |
8418 | 2.09M | if (sr.peek() == 'N' && sr.peek_at(1) == 's' && (sr.peek_at(2) == ' ' || sr.peek_at(2) == '\t')) { |
8419 | 21.3k | sr.advance(2); |
8420 | 21.3k | if (!sr_parseReal(sr, &material.shininess, 0.0, err, filename)) return false; |
8421 | 21.3k | sr.skip_line(); |
8422 | 21.3k | continue; |
8423 | 21.3k | } |
8424 | | |
8425 | | // illum model |
8426 | 2.07M | if (sr.match("illum", 5) && (sr.peek_at(5) == ' ' || sr.peek_at(5) == '\t')) { |
8427 | 6.14k | sr.advance(6); |
8428 | 6.14k | if (!sr_parseInt(sr, &material.illum, err, filename)) return false; |
8429 | 6.10k | sr.skip_line(); |
8430 | 6.10k | continue; |
8431 | 6.14k | } |
8432 | | |
8433 | | // dissolve |
8434 | 2.06M | if (sr.peek() == 'd' && (sr.peek_at(1) == ' ' || sr.peek_at(1) == '\t')) { |
8435 | 13.7k | sr.advance(1); |
8436 | 13.7k | if (!sr_parseReal(sr, &material.dissolve, 0.0, err, filename)) return false; |
8437 | | |
8438 | 13.7k | if (has_tr) { |
8439 | 9.70k | warn_ss << "Both `d` and `Tr` parameters defined for \"" |
8440 | 9.70k | << material.name |
8441 | 9.70k | << "\". Use the value of `d` for dissolve (line " << line_num |
8442 | 9.70k | << " in .mtl.)\n"; |
8443 | 9.70k | } |
8444 | 13.7k | has_d = true; |
8445 | 13.7k | sr.skip_line(); |
8446 | 13.7k | continue; |
8447 | 13.7k | } |
8448 | 2.05M | if (sr.peek() == 'T' && sr.peek_at(1) == 'r' && (sr.peek_at(2) == ' ' || sr.peek_at(2) == '\t')) { |
8449 | 56.2k | sr.advance(2); |
8450 | 56.2k | if (has_d) { |
8451 | 27.1k | warn_ss << "Both `d` and `Tr` parameters defined for \"" |
8452 | 27.1k | << material.name |
8453 | 27.1k | << "\". Use the value of `d` for dissolve (line " << line_num |
8454 | 27.1k | << " in .mtl.)\n"; |
8455 | 29.1k | } else { |
8456 | 29.1k | real_t tr_val; |
8457 | 29.1k | if (!sr_parseReal(sr, &tr_val, 0.0, err, filename)) return false; |
8458 | 29.1k | material.dissolve = static_cast<real_t>(1.0) - tr_val; |
8459 | 29.1k | } |
8460 | 56.2k | has_tr = true; |
8461 | 56.2k | sr.skip_line(); |
8462 | 56.2k | continue; |
8463 | 56.2k | } |
8464 | | |
8465 | | // PBR: roughness |
8466 | 1.99M | if (sr.peek() == 'P' && sr.peek_at(1) == 'r' && (sr.peek_at(2) == ' ' || sr.peek_at(2) == '\t')) { |
8467 | 4.40k | sr.advance(2); |
8468 | 4.40k | if (!sr_parseReal(sr, &material.roughness, 0.0, err, filename)) return false; |
8469 | 4.40k | sr.skip_line(); |
8470 | 4.40k | continue; |
8471 | 4.40k | } |
8472 | | |
8473 | | // PBR: metallic |
8474 | 1.99M | if (sr.peek() == 'P' && sr.peek_at(1) == 'm' && (sr.peek_at(2) == ' ' || sr.peek_at(2) == '\t')) { |
8475 | 2.07k | sr.advance(2); |
8476 | 2.07k | if (!sr_parseReal(sr, &material.metallic, 0.0, err, filename)) return false; |
8477 | 2.06k | sr.skip_line(); |
8478 | 2.06k | continue; |
8479 | 2.07k | } |
8480 | | |
8481 | | // PBR: sheen |
8482 | 1.99M | if (sr.peek() == 'P' && sr.peek_at(1) == 's' && (sr.peek_at(2) == ' ' || sr.peek_at(2) == '\t')) { |
8483 | 2.32k | sr.advance(2); |
8484 | 2.32k | if (!sr_parseReal(sr, &material.sheen, 0.0, err, filename)) return false; |
8485 | 2.31k | sr.skip_line(); |
8486 | 2.31k | continue; |
8487 | 2.32k | } |
8488 | | |
8489 | | // PBR: clearcoat thickness |
8490 | 1.98M | if (sr.peek() == 'P' && sr.peek_at(1) == 'c' && (sr.peek_at(2) == ' ' || sr.peek_at(2) == '\t')) { |
8491 | 2.89k | sr.advance(2); |
8492 | 2.89k | if (!sr_parseReal(sr, &material.clearcoat_thickness, 0.0, err, filename)) return false; |
8493 | 2.88k | sr.skip_line(); |
8494 | 2.88k | continue; |
8495 | 2.89k | } |
8496 | | |
8497 | | // PBR: clearcoat roughness |
8498 | 1.98M | if (sr.match("Pcr", 3) && (sr.peek_at(3) == ' ' || sr.peek_at(3) == '\t')) { |
8499 | 12.8k | sr.advance(4); |
8500 | 12.8k | if (!sr_parseReal(sr, &material.clearcoat_roughness, 0.0, err, filename)) return false; |
8501 | 12.7k | sr.skip_line(); |
8502 | 12.7k | continue; |
8503 | 12.8k | } |
8504 | | |
8505 | | // PBR: anisotropy |
8506 | 1.97M | if (sr.match("aniso", 5) && (sr.peek_at(5) == ' ' || sr.peek_at(5) == '\t')) { |
8507 | 1.51k | sr.advance(6); |
8508 | 1.51k | if (!sr_parseReal(sr, &material.anisotropy, 0.0, err, filename)) return false; |
8509 | 1.51k | sr.skip_line(); |
8510 | 1.51k | continue; |
8511 | 1.51k | } |
8512 | | |
8513 | | // PBR: anisotropy rotation |
8514 | 1.97M | if (sr.match("anisor", 6) && (sr.peek_at(6) == ' ' || sr.peek_at(6) == '\t')) { |
8515 | 7.11k | sr.advance(7); |
8516 | 7.11k | if (!sr_parseReal(sr, &material.anisotropy_rotation, 0.0, err, filename)) return false; |
8517 | 7.10k | sr.skip_line(); |
8518 | 7.10k | continue; |
8519 | 7.11k | } |
8520 | | |
8521 | | // For texture directives, read rest of line and delegate to |
8522 | | // ParseTextureNameAndOption (which uses the old const char* parse functions). |
8523 | | |
8524 | | // ambient or ambient occlusion texture |
8525 | 1.96M | if (sr.match("map_Ka", 6) && (sr.peek_at(6) == ' ' || sr.peek_at(6) == '\t')) { |
8526 | 3.17k | sr.advance(7); |
8527 | 3.17k | std::string line_rest = trimTrailingWhitespace(sr.read_line()); |
8528 | 3.17k | ParseTextureNameAndOption(&(material.ambient_texname), |
8529 | 3.17k | &(material.ambient_texopt), line_rest.c_str()); |
8530 | 3.17k | sr.skip_line(); |
8531 | 3.17k | continue; |
8532 | 3.17k | } |
8533 | | |
8534 | | // diffuse texture |
8535 | 1.96M | if (sr.match("map_Kd", 6) && (sr.peek_at(6) == ' ' || sr.peek_at(6) == '\t')) { |
8536 | 12.9k | sr.advance(7); |
8537 | 12.9k | std::string line_rest = trimTrailingWhitespace(sr.read_line()); |
8538 | 12.9k | ParseTextureNameAndOption(&(material.diffuse_texname), |
8539 | 12.9k | &(material.diffuse_texopt), line_rest.c_str()); |
8540 | 12.9k | if (!has_kd) { |
8541 | 12.6k | material.diffuse[0] = static_cast<real_t>(0.6); |
8542 | 12.6k | material.diffuse[1] = static_cast<real_t>(0.6); |
8543 | 12.6k | material.diffuse[2] = static_cast<real_t>(0.6); |
8544 | 12.6k | } |
8545 | 12.9k | sr.skip_line(); |
8546 | 12.9k | continue; |
8547 | 12.9k | } |
8548 | | |
8549 | | // specular texture |
8550 | 1.94M | if (sr.match("map_Ks", 6) && (sr.peek_at(6) == ' ' || sr.peek_at(6) == '\t')) { |
8551 | 2.01k | sr.advance(7); |
8552 | 2.01k | std::string line_rest = trimTrailingWhitespace(sr.read_line()); |
8553 | 2.01k | ParseTextureNameAndOption(&(material.specular_texname), |
8554 | 2.01k | &(material.specular_texopt), line_rest.c_str()); |
8555 | 2.01k | sr.skip_line(); |
8556 | 2.01k | continue; |
8557 | 2.01k | } |
8558 | | |
8559 | | // specular highlight texture |
8560 | 1.94M | if (sr.match("map_Ns", 6) && (sr.peek_at(6) == ' ' || sr.peek_at(6) == '\t')) { |
8561 | 3.55k | sr.advance(7); |
8562 | 3.55k | std::string line_rest = trimTrailingWhitespace(sr.read_line()); |
8563 | 3.55k | ParseTextureNameAndOption(&(material.specular_highlight_texname), |
8564 | 3.55k | &(material.specular_highlight_texopt), line_rest.c_str()); |
8565 | 3.55k | sr.skip_line(); |
8566 | 3.55k | continue; |
8567 | 3.55k | } |
8568 | | |
8569 | | // bump texture |
8570 | 1.94M | if ((sr.match("map_bump", 8) || sr.match("map_Bump", 8)) && |
8571 | 6.50k | (sr.peek_at(8) == ' ' || sr.peek_at(8) == '\t')) { |
8572 | 3.93k | sr.advance(9); |
8573 | 3.93k | std::string line_rest = trimTrailingWhitespace(sr.read_line()); |
8574 | 3.93k | ParseTextureNameAndOption(&(material.bump_texname), |
8575 | 3.93k | &(material.bump_texopt), line_rest.c_str()); |
8576 | 3.93k | sr.skip_line(); |
8577 | 3.93k | continue; |
8578 | 3.93k | } |
8579 | | |
8580 | | // bump texture (short form) |
8581 | 1.93M | if (sr.match("bump", 4) && (sr.peek_at(4) == ' ' || sr.peek_at(4) == '\t')) { |
8582 | 16.1k | sr.advance(5); |
8583 | 16.1k | std::string line_rest = trimTrailingWhitespace(sr.read_line()); |
8584 | 16.1k | ParseTextureNameAndOption(&(material.bump_texname), |
8585 | 16.1k | &(material.bump_texopt), line_rest.c_str()); |
8586 | 16.1k | sr.skip_line(); |
8587 | 16.1k | continue; |
8588 | 16.1k | } |
8589 | | |
8590 | | // alpha texture |
8591 | 1.92M | if (sr.match("map_d", 5) && (sr.peek_at(5) == ' ' || sr.peek_at(5) == '\t')) { |
8592 | 4.86k | sr.advance(6); |
8593 | 4.86k | std::string line_rest = trimTrailingWhitespace(sr.read_line()); |
8594 | 4.86k | ParseTextureNameAndOption(&(material.alpha_texname), |
8595 | 4.86k | &(material.alpha_texopt), line_rest.c_str()); |
8596 | 4.86k | sr.skip_line(); |
8597 | 4.86k | continue; |
8598 | 4.86k | } |
8599 | | |
8600 | | // displacement texture |
8601 | 1.91M | if ((sr.match("map_disp", 8) || sr.match("map_Disp", 8)) && |
8602 | 28.5k | (sr.peek_at(8) == ' ' || sr.peek_at(8) == '\t')) { |
8603 | 24.7k | sr.advance(9); |
8604 | 24.7k | std::string line_rest = trimTrailingWhitespace(sr.read_line()); |
8605 | 24.7k | ParseTextureNameAndOption(&(material.displacement_texname), |
8606 | 24.7k | &(material.displacement_texopt), line_rest.c_str()); |
8607 | 24.7k | sr.skip_line(); |
8608 | 24.7k | continue; |
8609 | 24.7k | } |
8610 | | |
8611 | | // displacement texture (short form) |
8612 | 1.89M | if (sr.match("disp", 4) && (sr.peek_at(4) == ' ' || sr.peek_at(4) == '\t')) { |
8613 | 33.4k | sr.advance(5); |
8614 | 33.4k | std::string line_rest = trimTrailingWhitespace(sr.read_line()); |
8615 | 33.4k | ParseTextureNameAndOption(&(material.displacement_texname), |
8616 | 33.4k | &(material.displacement_texopt), line_rest.c_str()); |
8617 | 33.4k | sr.skip_line(); |
8618 | 33.4k | continue; |
8619 | 33.4k | } |
8620 | | |
8621 | | // reflection map |
8622 | 1.85M | if (sr.match("refl", 4) && (sr.peek_at(4) == ' ' || sr.peek_at(4) == '\t')) { |
8623 | 128k | sr.advance(5); |
8624 | 128k | std::string line_rest = trimTrailingWhitespace(sr.read_line()); |
8625 | 128k | ParseTextureNameAndOption(&(material.reflection_texname), |
8626 | 128k | &(material.reflection_texopt), line_rest.c_str()); |
8627 | 128k | sr.skip_line(); |
8628 | 128k | continue; |
8629 | 128k | } |
8630 | | |
8631 | | // PBR: roughness texture |
8632 | 1.73M | if (sr.match("map_Pr", 6) && (sr.peek_at(6) == ' ' || sr.peek_at(6) == '\t')) { |
8633 | 4.58k | sr.advance(7); |
8634 | 4.58k | std::string line_rest = trimTrailingWhitespace(sr.read_line()); |
8635 | 4.58k | ParseTextureNameAndOption(&(material.roughness_texname), |
8636 | 4.58k | &(material.roughness_texopt), line_rest.c_str()); |
8637 | 4.58k | sr.skip_line(); |
8638 | 4.58k | continue; |
8639 | 4.58k | } |
8640 | | |
8641 | | // PBR: metallic texture |
8642 | 1.72M | if (sr.match("map_Pm", 6) && (sr.peek_at(6) == ' ' || sr.peek_at(6) == '\t')) { |
8643 | 5.32k | sr.advance(7); |
8644 | 5.32k | std::string line_rest = trimTrailingWhitespace(sr.read_line()); |
8645 | 5.32k | ParseTextureNameAndOption(&(material.metallic_texname), |
8646 | 5.32k | &(material.metallic_texopt), line_rest.c_str()); |
8647 | 5.32k | sr.skip_line(); |
8648 | 5.32k | continue; |
8649 | 5.32k | } |
8650 | | |
8651 | | // PBR: sheen texture |
8652 | 1.72M | if (sr.match("map_Ps", 6) && (sr.peek_at(6) == ' ' || sr.peek_at(6) == '\t')) { |
8653 | 92.4k | sr.advance(7); |
8654 | 92.4k | std::string line_rest = trimTrailingWhitespace(sr.read_line()); |
8655 | 92.4k | ParseTextureNameAndOption(&(material.sheen_texname), |
8656 | 92.4k | &(material.sheen_texopt), line_rest.c_str()); |
8657 | 92.4k | sr.skip_line(); |
8658 | 92.4k | continue; |
8659 | 92.4k | } |
8660 | | |
8661 | | // PBR: emissive texture |
8662 | 1.62M | if (sr.match("map_Ke", 6) && (sr.peek_at(6) == ' ' || sr.peek_at(6) == '\t')) { |
8663 | 14.5k | sr.advance(7); |
8664 | 14.5k | std::string line_rest = trimTrailingWhitespace(sr.read_line()); |
8665 | 14.5k | ParseTextureNameAndOption(&(material.emissive_texname), |
8666 | 14.5k | &(material.emissive_texopt), line_rest.c_str()); |
8667 | 14.5k | sr.skip_line(); |
8668 | 14.5k | continue; |
8669 | 14.5k | } |
8670 | | |
8671 | | // PBR: normal map texture |
8672 | 1.61M | if (sr.match("norm", 4) && (sr.peek_at(4) == ' ' || sr.peek_at(4) == '\t')) { |
8673 | 20.4k | sr.advance(5); |
8674 | 20.4k | std::string line_rest = trimTrailingWhitespace(sr.read_line()); |
8675 | 20.4k | ParseTextureNameAndOption(&(material.normal_texname), |
8676 | 20.4k | &(material.normal_texopt), line_rest.c_str()); |
8677 | 20.4k | sr.skip_line(); |
8678 | 20.4k | continue; |
8679 | 20.4k | } |
8680 | | |
8681 | | // unknown parameter |
8682 | 1.59M | { |
8683 | 1.59M | std::string line_rest = trimTrailingWhitespace(sr.read_line()); |
8684 | 1.59M | const char *_lp = line_rest.c_str(); |
8685 | 1.59M | const char *_space = strchr(_lp, ' '); |
8686 | 1.59M | if (!_space) { |
8687 | 1.21M | _space = strchr(_lp, '\t'); |
8688 | 1.21M | } |
8689 | 1.59M | if (_space) { |
8690 | 629k | std::ptrdiff_t len = _space - _lp; |
8691 | 629k | std::string key(_lp, static_cast<size_t>(len)); |
8692 | 629k | std::string value = _space + 1; |
8693 | 629k | material.unknown_parameter.insert( |
8694 | 629k | std::pair<std::string, std::string>(key, value)); |
8695 | 629k | } |
8696 | 1.59M | } |
8697 | 1.59M | sr.skip_line(); |
8698 | 1.59M | } |
8699 | | // flush last material (only if it was actually defined). |
8700 | 11.6k | if (!material.name.empty()) { |
8701 | 1.29k | material_map->insert(std::pair<std::string, int>( |
8702 | 1.29k | material.name, static_cast<int>(materials->size()))); |
8703 | 1.29k | materials->push_back(material); |
8704 | 1.29k | } |
8705 | | |
8706 | 11.6k | if (warning) { |
8707 | 11.6k | (*warning) += warn_ss.str(); |
8708 | 11.6k | } |
8709 | | |
8710 | 11.6k | return true; |
8711 | 11.8k | } |
8712 | | |
8713 | | void LoadMtl(std::map<std::string, int> *material_map, |
8714 | | std::vector<material_t> *materials, std::istream *inStream, |
8715 | 0 | std::string *warning, std::string *err) { |
8716 | 0 | StreamReader sr(*inStream); |
8717 | 0 | LoadMtlInternal(material_map, materials, sr, warning, err); |
8718 | 0 | } |
8719 | | |
8720 | | |
8721 | | bool MaterialFileReader::operator()(const std::string &matId, |
8722 | | std::vector<material_t> *materials, |
8723 | | std::map<std::string, int> *matMap, |
8724 | 0 | std::string *warn, std::string *err) { |
8725 | 0 | if (!m_mtlBaseDir.empty()) { |
8726 | | #ifdef _WIN32 |
8727 | | char sep = ';'; |
8728 | | #else |
8729 | 0 | char sep = ':'; |
8730 | 0 | #endif |
8731 | | |
8732 | | // https://stackoverflow.com/questions/5167625/splitting-a-c-stdstring-using-tokens-e-g |
8733 | 0 | std::vector<std::string> paths; |
8734 | 0 | std::istringstream f(m_mtlBaseDir); |
8735 | |
|
8736 | 0 | std::string s; |
8737 | 0 | while (getline(f, s, sep)) { |
8738 | 0 | paths.push_back(s); |
8739 | 0 | } |
8740 | |
|
8741 | 0 | for (size_t i = 0; i < paths.size(); i++) { |
8742 | 0 | std::string filepath = JoinPath(paths[i], matId); |
8743 | |
|
8744 | | #ifdef TINYOBJLOADER_USE_MMAP |
8745 | | { |
8746 | | MappedFile mf; |
8747 | | if (!mf.open(filepath.c_str())) continue; |
8748 | | if (mf.size > TINYOBJLOADER_STREAM_READER_MAX_BYTES) { |
8749 | | if (err) { |
8750 | | std::stringstream ss; |
8751 | | ss << "input stream too large (" << mf.size |
8752 | | << " bytes exceeds limit " |
8753 | | << TINYOBJLOADER_STREAM_READER_MAX_BYTES << " bytes)\n"; |
8754 | | (*err) += ss.str(); |
8755 | | } |
8756 | | return false; |
8757 | | } |
8758 | | StreamReader sr(mf.data, mf.size); |
8759 | | return LoadMtlInternal(matMap, materials, sr, warn, err, filepath); |
8760 | | } |
8761 | | #else // !TINYOBJLOADER_USE_MMAP |
8762 | | #ifdef _WIN32 |
8763 | | std::ifstream matIStream(LongPathW(UTF8ToWchar(filepath)).c_str()); |
8764 | | #else |
8765 | 0 | std::ifstream matIStream(filepath.c_str()); |
8766 | 0 | #endif |
8767 | 0 | if (matIStream) { |
8768 | 0 | StreamReader mtl_sr(matIStream); |
8769 | 0 | return LoadMtlInternal(matMap, materials, mtl_sr, warn, err, filepath); |
8770 | 0 | } |
8771 | 0 | #endif // TINYOBJLOADER_USE_MMAP |
8772 | 0 | } |
8773 | | |
8774 | 0 | std::stringstream ss; |
8775 | 0 | ss << "Material file [ " << matId |
8776 | 0 | << " ] not found in a path : " << m_mtlBaseDir << "\n"; |
8777 | 0 | if (warn) { |
8778 | 0 | (*warn) += ss.str(); |
8779 | 0 | } |
8780 | 0 | return false; |
8781 | |
|
8782 | 0 | } else { |
8783 | 0 | std::string filepath = matId; |
8784 | |
|
8785 | | #ifdef TINYOBJLOADER_USE_MMAP |
8786 | | { |
8787 | | MappedFile mf; |
8788 | | if (mf.open(filepath.c_str())) { |
8789 | | if (mf.size > TINYOBJLOADER_STREAM_READER_MAX_BYTES) { |
8790 | | if (err) { |
8791 | | std::stringstream ss; |
8792 | | ss << "input stream too large (" << mf.size |
8793 | | << " bytes exceeds limit " |
8794 | | << TINYOBJLOADER_STREAM_READER_MAX_BYTES << " bytes)\n"; |
8795 | | (*err) += ss.str(); |
8796 | | } |
8797 | | return false; |
8798 | | } |
8799 | | StreamReader sr(mf.data, mf.size); |
8800 | | return LoadMtlInternal(matMap, materials, sr, warn, err, filepath); |
8801 | | } |
8802 | | } |
8803 | | #else // !TINYOBJLOADER_USE_MMAP |
8804 | | #ifdef _WIN32 |
8805 | | std::ifstream matIStream(LongPathW(UTF8ToWchar(filepath)).c_str()); |
8806 | | #else |
8807 | 0 | std::ifstream matIStream(filepath.c_str()); |
8808 | 0 | #endif |
8809 | 0 | if (matIStream) { |
8810 | 0 | StreamReader mtl_sr(matIStream); |
8811 | 0 | return LoadMtlInternal(matMap, materials, mtl_sr, warn, err, filepath); |
8812 | 0 | } |
8813 | 0 | #endif // TINYOBJLOADER_USE_MMAP |
8814 | | |
8815 | 0 | std::stringstream ss; |
8816 | 0 | ss << "Material file [ " << filepath |
8817 | 0 | << " ] not found in a path : " << m_mtlBaseDir << "\n"; |
8818 | 0 | if (warn) { |
8819 | 0 | (*warn) += ss.str(); |
8820 | 0 | } |
8821 | |
|
8822 | 0 | return false; |
8823 | 0 | } |
8824 | 0 | } |
8825 | | |
8826 | | bool MaterialStreamReader::operator()(const std::string &matId, |
8827 | | std::vector<material_t> *materials, |
8828 | | std::map<std::string, int> *matMap, |
8829 | 11.8k | std::string *warn, std::string *err) { |
8830 | 11.8k | (void)matId; |
8831 | 11.8k | if (!m_inStream) { |
8832 | 0 | std::stringstream ss; |
8833 | 0 | ss << "Material stream in error state. \n"; |
8834 | 0 | if (warn) { |
8835 | 0 | (*warn) += ss.str(); |
8836 | 0 | } |
8837 | 0 | return false; |
8838 | 0 | } |
8839 | | |
8840 | 11.8k | StreamReader mtl_sr(m_inStream); |
8841 | 11.8k | return LoadMtlInternal(matMap, materials, mtl_sr, warn, err, "<stream>"); |
8842 | 11.8k | } |
8843 | | |
8844 | | static bool LoadObjInternal(attrib_t *attrib, std::vector<shape_t> *shapes, |
8845 | | std::vector<material_t> *materials, |
8846 | | std::string *warn, std::string *err, |
8847 | | StreamReader &sr, |
8848 | | MaterialReader *readMatFn, bool triangulate, |
8849 | | bool default_vcols_fallback, |
8850 | 11.4k | const std::string &filename = "<stream>") { |
8851 | 11.4k | if (sr.has_errors()) { |
8852 | 0 | if (err) { |
8853 | 0 | (*err) += sr.get_errors(); |
8854 | 0 | } |
8855 | 0 | return false; |
8856 | 0 | } |
8857 | | |
8858 | 11.4k | std::vector<real_t> v; |
8859 | 11.4k | std::vector<real_t> vertex_weights; |
8860 | 11.4k | std::vector<real_t> vn; |
8861 | 11.4k | std::vector<real_t> vt; |
8862 | 11.4k | std::vector<real_t> vt_w; // optional [w] component in `vt` |
8863 | 11.4k | std::vector<real_t> vc; |
8864 | 11.4k | std::vector<skin_weight_t> vw; |
8865 | 11.4k | std::vector<tag_t> tags; |
8866 | 11.4k | PrimGroup prim_group; |
8867 | 11.4k | std::string name; |
8868 | | |
8869 | | // material |
8870 | 11.4k | std::set<std::string> material_filenames; |
8871 | 11.4k | std::map<std::string, int> material_map; |
8872 | 11.4k | int material = -1; |
8873 | | |
8874 | 11.4k | unsigned int current_smoothing_id = 0; |
8875 | | |
8876 | 11.4k | int greatest_v_idx = -1; |
8877 | 11.4k | int greatest_vn_idx = -1; |
8878 | 11.4k | int greatest_vt_idx = -1; |
8879 | | |
8880 | 11.4k | shape_t shape; |
8881 | | |
8882 | 11.4k | bool found_all_colors = true; |
8883 | | |
8884 | | // Handle BOM |
8885 | 11.4k | if (sr.remaining() >= 3 && |
8886 | 11.2k | static_cast<unsigned char>(sr.peek()) == 0xEF && |
8887 | 66 | static_cast<unsigned char>(sr.peek_at(1)) == 0xBB && |
8888 | 49 | static_cast<unsigned char>(sr.peek_at(2)) == 0xBF) { |
8889 | 35 | sr.advance(3); |
8890 | 35 | } |
8891 | | |
8892 | 11.4k | warning_context context; |
8893 | 11.4k | context.warn = warn; |
8894 | 11.4k | context.filename = filename; |
8895 | | |
8896 | 6.51M | while (!sr.eof()) { |
8897 | 6.50M | sr.skip_space(); |
8898 | 6.50M | if (sr.at_line_end()) { sr.skip_line(); continue; } |
8899 | 5.95M | if (sr.peek() == '#') { sr.skip_line(); continue; } |
8900 | | |
8901 | 5.91M | size_t line_num = sr.line_num(); |
8902 | | |
8903 | | // vertex |
8904 | 5.91M | if (sr.peek() == 'v' && (sr.peek_at(1) == ' ' || sr.peek_at(1) == '\t')) { |
8905 | 888k | sr.advance(2); |
8906 | 888k | real_t x, y, z; |
8907 | 888k | real_t r, g, b; |
8908 | | |
8909 | 888k | int num_components = sr_parseVertexWithColor(&x, &y, &z, &r, &g, &b, sr, err, filename); |
8910 | 888k | if (num_components < 0) return false; |
8911 | 888k | found_all_colors &= (num_components == 6); |
8912 | | |
8913 | 888k | v.push_back(x); |
8914 | 888k | v.push_back(y); |
8915 | 888k | v.push_back(z); |
8916 | | |
8917 | 888k | vertex_weights.push_back(r); |
8918 | | |
8919 | 888k | if ((num_components == 6) || default_vcols_fallback) { |
8920 | 888k | vc.push_back(r); |
8921 | 888k | vc.push_back(g); |
8922 | 888k | vc.push_back(b); |
8923 | 888k | } |
8924 | | |
8925 | 888k | sr.skip_line(); |
8926 | 888k | continue; |
8927 | 888k | } |
8928 | | |
8929 | | // normal |
8930 | 5.02M | if (sr.peek() == 'v' && sr.peek_at(1) == 'n' && (sr.peek_at(2) == ' ' || sr.peek_at(2) == '\t')) { |
8931 | 516k | sr.advance(3); |
8932 | 516k | real_t x, y, z; |
8933 | 516k | if (!sr_parseReal3(&x, &y, &z, sr, err, filename)) return false; |
8934 | 516k | vn.push_back(x); |
8935 | 516k | vn.push_back(y); |
8936 | 516k | vn.push_back(z); |
8937 | 516k | sr.skip_line(); |
8938 | 516k | continue; |
8939 | 516k | } |
8940 | | |
8941 | | // texcoord |
8942 | 4.50M | if (sr.peek() == 'v' && sr.peek_at(1) == 't' && (sr.peek_at(2) == ' ' || sr.peek_at(2) == '\t')) { |
8943 | 359k | sr.advance(3); |
8944 | 359k | real_t x, y; |
8945 | 359k | if (!sr_parseReal2(&x, &y, sr, err, filename)) return false; |
8946 | 359k | vt.push_back(x); |
8947 | 359k | vt.push_back(y); |
8948 | | |
8949 | | // Parse optional w component |
8950 | 359k | real_t w = static_cast<real_t>(0.0); |
8951 | 359k | sr_parseReal(sr, &w); |
8952 | 359k | vt_w.push_back(w); |
8953 | | |
8954 | 359k | sr.skip_line(); |
8955 | 359k | continue; |
8956 | 359k | } |
8957 | | |
8958 | | // skin weight. tinyobj extension |
8959 | 4.14M | if (sr.peek() == 'v' && sr.peek_at(1) == 'w' && (sr.peek_at(2) == ' ' || sr.peek_at(2) == '\t')) { |
8960 | 79.5k | sr.advance(3); |
8961 | | |
8962 | 79.5k | int vid; |
8963 | 79.5k | if (!sr_parseInt(sr, &vid, err, filename)) return false; |
8964 | | |
8965 | 79.3k | skin_weight_t sw; |
8966 | 79.3k | sw.vertex_id = vid; |
8967 | | |
8968 | 79.3k | size_t vw_loop_max = sr.remaining() + 1; |
8969 | 79.3k | size_t vw_loop_iter = 0; |
8970 | 700k | while (!sr.at_line_end() && sr.peek() != '#' && |
8971 | 621k | vw_loop_iter < vw_loop_max) { |
8972 | 621k | real_t j, w; |
8973 | 621k | sr_parseReal2(&j, &w, sr, -1.0); |
8974 | | |
8975 | 621k | if (j < static_cast<real_t>(0)) { |
8976 | 125 | if (err) { |
8977 | 125 | (*err) += sr.format_error(filename, |
8978 | 125 | "failed to parse `vw' line: joint_id is negative"); |
8979 | 125 | } |
8980 | 125 | return false; |
8981 | 125 | } |
8982 | | |
8983 | | // Clamp to int range to avoid UB on float-to-int overflow. |
8984 | 621k | if (j > static_cast<real_t>(std::numeric_limits<int>::max())) { |
8985 | 18 | if (err) { |
8986 | 18 | (*err) += sr.format_error(filename, |
8987 | 18 | "failed to parse `vw' line: joint_id overflow"); |
8988 | 18 | } |
8989 | 18 | return false; |
8990 | 18 | } |
8991 | 621k | joint_and_weight_t jw; |
8992 | 621k | jw.joint_id = static_cast<int>(j); |
8993 | 621k | jw.weight = w; |
8994 | | |
8995 | 621k | sw.weightValues.push_back(jw); |
8996 | 621k | sr.skip_space_and_cr(); |
8997 | 621k | vw_loop_iter++; |
8998 | 621k | } |
8999 | | |
9000 | 79.2k | vw.push_back(sw); |
9001 | 79.2k | sr.skip_line(); |
9002 | 79.2k | continue; |
9003 | 79.3k | } |
9004 | | |
9005 | 4.06M | context.line_number = line_num; |
9006 | | |
9007 | | // line |
9008 | 4.06M | if (sr.peek() == 'l' && (sr.peek_at(1) == ' ' || sr.peek_at(1) == '\t')) { |
9009 | 526k | sr.advance(2); |
9010 | | |
9011 | 526k | __line_t line; |
9012 | | |
9013 | 526k | size_t l_loop_max = sr.remaining() + 1; |
9014 | 526k | size_t l_loop_iter = 0; |
9015 | 610k | while (!sr.at_line_end() && sr.peek() != '#' && |
9016 | 84.2k | l_loop_iter < l_loop_max) { |
9017 | 84.2k | vertex_index_t vi; |
9018 | 84.2k | if (!sr_parseTriple(sr, size_to_int(v.size() / 3), |
9019 | 84.2k | size_to_int(vn.size() / 3), |
9020 | 84.2k | size_to_int(vt.size() / 2), &vi, context)) { |
9021 | 108 | if (err) { |
9022 | 108 | (*err) += sr.format_error(filename, |
9023 | 108 | "failed to parse `l' line (invalid vertex index)"); |
9024 | 108 | } |
9025 | 108 | return false; |
9026 | 108 | } |
9027 | | |
9028 | 84.0k | line.vertex_indices.push_back(vi); |
9029 | 84.0k | sr.skip_space_and_cr(); |
9030 | 84.0k | l_loop_iter++; |
9031 | 84.0k | } |
9032 | | |
9033 | 526k | prim_group.lineGroup.push_back(line); |
9034 | 526k | sr.skip_line(); |
9035 | 526k | continue; |
9036 | 526k | } |
9037 | | |
9038 | | // points |
9039 | 3.54M | if (sr.peek() == 'p' && (sr.peek_at(1) == ' ' || sr.peek_at(1) == '\t')) { |
9040 | 515k | sr.advance(2); |
9041 | | |
9042 | 515k | __points_t pts; |
9043 | | |
9044 | 515k | size_t p_loop_max = sr.remaining() + 1; |
9045 | 515k | size_t p_loop_iter = 0; |
9046 | 3.13M | while (!sr.at_line_end() && sr.peek() != '#' && |
9047 | 2.62M | p_loop_iter < p_loop_max) { |
9048 | 2.62M | vertex_index_t vi; |
9049 | 2.62M | if (!sr_parseTriple(sr, size_to_int(v.size() / 3), |
9050 | 2.62M | size_to_int(vn.size() / 3), |
9051 | 2.62M | size_to_int(vt.size() / 2), &vi, context)) { |
9052 | 97 | if (err) { |
9053 | 97 | (*err) += sr.format_error(filename, |
9054 | 97 | "failed to parse `p' line (invalid vertex index)"); |
9055 | 97 | } |
9056 | 97 | return false; |
9057 | 97 | } |
9058 | | |
9059 | 2.62M | pts.vertex_indices.push_back(vi); |
9060 | 2.62M | sr.skip_space_and_cr(); |
9061 | 2.62M | p_loop_iter++; |
9062 | 2.62M | } |
9063 | | |
9064 | 515k | prim_group.pointsGroup.push_back(pts); |
9065 | 515k | sr.skip_line(); |
9066 | 515k | continue; |
9067 | 515k | } |
9068 | | |
9069 | | // face |
9070 | 3.02M | if (sr.peek() == 'f' && (sr.peek_at(1) == ' ' || sr.peek_at(1) == '\t')) { |
9071 | 529k | sr.advance(2); |
9072 | 529k | sr.skip_space(); |
9073 | | |
9074 | 529k | face_t face; |
9075 | | |
9076 | 529k | face.smoothing_group_id = current_smoothing_id; |
9077 | 529k | face.vertex_indices.reserve(3); |
9078 | | |
9079 | 529k | size_t f_loop_max = sr.remaining() + 1; |
9080 | 529k | size_t f_loop_iter = 0; |
9081 | 2.79M | while (!sr.at_line_end() && sr.peek() != '#' && |
9082 | 2.26M | f_loop_iter < f_loop_max) { |
9083 | 2.26M | vertex_index_t vi; |
9084 | 2.26M | if (!sr_parseTriple(sr, size_to_int(v.size() / 3), |
9085 | 2.26M | size_to_int(vn.size() / 3), |
9086 | 2.26M | size_to_int(vt.size() / 2), &vi, context)) { |
9087 | 232 | if (err) { |
9088 | 232 | (*err) += sr.format_error(filename, |
9089 | 232 | "failed to parse `f' line (invalid vertex index)"); |
9090 | 232 | } |
9091 | 232 | return false; |
9092 | 232 | } |
9093 | | |
9094 | 2.26M | greatest_v_idx = greatest_v_idx > vi.v_idx ? greatest_v_idx : vi.v_idx; |
9095 | 2.26M | greatest_vn_idx = |
9096 | 2.26M | greatest_vn_idx > vi.vn_idx ? greatest_vn_idx : vi.vn_idx; |
9097 | 2.26M | greatest_vt_idx = |
9098 | 2.26M | greatest_vt_idx > vi.vt_idx ? greatest_vt_idx : vi.vt_idx; |
9099 | | |
9100 | 2.26M | face.vertex_indices.push_back(vi); |
9101 | 2.26M | sr.skip_space_and_cr(); |
9102 | 2.26M | f_loop_iter++; |
9103 | 2.26M | } |
9104 | | |
9105 | 529k | prim_group.faceGroup.push_back(face); |
9106 | 529k | sr.skip_line(); |
9107 | 529k | continue; |
9108 | 529k | } |
9109 | | |
9110 | | // use mtl |
9111 | 2.49M | if (sr.match("usemtl", 6) && (sr.peek_at(6) == ' ' || sr.peek_at(6) == '\t')) { |
9112 | 62.0k | sr.advance(6); |
9113 | 62.0k | std::string namebuf = sr_parseString(sr); |
9114 | | |
9115 | 62.0k | int newMaterialId = -1; |
9116 | 62.0k | std::map<std::string, int>::const_iterator it = |
9117 | 62.0k | material_map.find(namebuf); |
9118 | 62.0k | if (it != material_map.end()) { |
9119 | 18.3k | newMaterialId = it->second; |
9120 | 43.6k | } else { |
9121 | 43.6k | if (warn) { |
9122 | 43.6k | (*warn) += "material [ '" + namebuf + "' ] not found in .mtl\n"; |
9123 | 43.6k | } |
9124 | 43.6k | } |
9125 | | |
9126 | 62.0k | if (newMaterialId != material) { |
9127 | 26.2k | exportGroupsToShape(&shape, prim_group, tags, material, name, |
9128 | 26.2k | triangulate, v, warn); |
9129 | 26.2k | prim_group.faceGroup.clear(); |
9130 | 26.2k | material = newMaterialId; |
9131 | 26.2k | } |
9132 | | |
9133 | 62.0k | sr.skip_line(); |
9134 | 62.0k | continue; |
9135 | 62.0k | } |
9136 | | |
9137 | | // load mtl |
9138 | 2.43M | if (sr.match("mtllib", 6) && (sr.peek_at(6) == ' ' || sr.peek_at(6) == '\t')) { |
9139 | 61.0k | if (readMatFn) { |
9140 | 61.0k | sr.advance(7); |
9141 | | |
9142 | 61.0k | std::string line_rest = trimTrailingWhitespace(sr.read_line()); |
9143 | 61.0k | std::vector<std::string> filenames; |
9144 | 61.0k | SplitString(line_rest, ' ', '\\', filenames); |
9145 | 61.0k | RemoveEmptyTokens(&filenames); |
9146 | | |
9147 | 61.0k | if (filenames.empty()) { |
9148 | 7.43k | if (warn) { |
9149 | 7.43k | std::stringstream ss; |
9150 | 7.43k | ss << "Looks like empty filename for mtllib. Use default " |
9151 | 7.43k | "material (line " |
9152 | 7.43k | << line_num << ".)\n"; |
9153 | | |
9154 | 7.43k | (*warn) += ss.str(); |
9155 | 7.43k | } |
9156 | 53.6k | } else { |
9157 | 53.6k | bool found = false; |
9158 | 157k | for (size_t s = 0; s < filenames.size(); s++) { |
9159 | 115k | if (material_filenames.count(filenames[s]) > 0) { |
9160 | 103k | found = true; |
9161 | 103k | continue; |
9162 | 103k | } |
9163 | | |
9164 | 11.8k | std::string warn_mtl; |
9165 | 11.8k | std::string err_mtl; |
9166 | 11.8k | bool ok = (*readMatFn)(filenames[s].c_str(), materials, |
9167 | 11.8k | &material_map, &warn_mtl, &err_mtl); |
9168 | 11.8k | if (warn && (!warn_mtl.empty())) { |
9169 | 254 | (*warn) += warn_mtl; |
9170 | 254 | } |
9171 | | |
9172 | 11.8k | if (err && (!err_mtl.empty())) { |
9173 | 276 | (*err) += err_mtl; |
9174 | 276 | } |
9175 | | |
9176 | 11.8k | if (ok) { |
9177 | 11.6k | found = true; |
9178 | 11.6k | material_filenames.insert(filenames[s]); |
9179 | 11.6k | break; |
9180 | 11.6k | } |
9181 | 11.8k | } |
9182 | | |
9183 | 53.6k | if (!found) { |
9184 | 246 | if (warn) { |
9185 | 246 | (*warn) += |
9186 | 246 | "Failed to load material file(s). Use default " |
9187 | 246 | "material.\n"; |
9188 | 246 | } |
9189 | 246 | } |
9190 | 53.6k | } |
9191 | 61.0k | } |
9192 | | |
9193 | 61.0k | sr.skip_line(); |
9194 | 61.0k | continue; |
9195 | 61.0k | } |
9196 | | |
9197 | | // group name |
9198 | 2.37M | if (sr.peek() == 'g' && (sr.peek_at(1) == ' ' || sr.peek_at(1) == '\t')) { |
9199 | | // flush previous face group. |
9200 | 85.8k | bool ret = exportGroupsToShape(&shape, prim_group, tags, material, name, |
9201 | 85.8k | triangulate, v, warn); |
9202 | 85.8k | (void)ret; |
9203 | | |
9204 | 85.8k | if (shape.mesh.indices.size() > 0) { |
9205 | 3.82k | shapes->push_back(shape); |
9206 | 3.82k | } |
9207 | | |
9208 | 85.8k | shape = shape_t(); |
9209 | | |
9210 | | // material = -1; |
9211 | 85.8k | prim_group.clear(); |
9212 | | |
9213 | 85.8k | std::vector<std::string> names; |
9214 | | |
9215 | 85.8k | size_t g_loop_max = sr.remaining() + 1; |
9216 | 85.8k | size_t g_loop_iter = 0; |
9217 | 654k | while (!sr.at_line_end() && sr.peek() != '#' && |
9218 | 568k | g_loop_iter < g_loop_max) { |
9219 | 568k | std::string str = sr_parseString(sr); |
9220 | 568k | names.push_back(str); |
9221 | 568k | sr.skip_space_and_cr(); |
9222 | 568k | g_loop_iter++; |
9223 | 568k | } |
9224 | | |
9225 | | // names[0] must be 'g' |
9226 | | |
9227 | 85.8k | if (names.size() < 2) { |
9228 | | // 'g' with empty names |
9229 | 51.3k | if (warn) { |
9230 | 51.3k | std::stringstream ss; |
9231 | 51.3k | ss << "Empty group name. line: " << line_num << "\n"; |
9232 | 51.3k | (*warn) += ss.str(); |
9233 | 51.3k | name = ""; |
9234 | 51.3k | } |
9235 | 51.3k | } else { |
9236 | 34.4k | std::stringstream ss; |
9237 | 34.4k | ss << names[1]; |
9238 | | |
9239 | 483k | for (size_t i = 2; i < names.size(); i++) { |
9240 | 448k | ss << " " << names[i]; |
9241 | 448k | } |
9242 | | |
9243 | 34.4k | name = ss.str(); |
9244 | 34.4k | } |
9245 | | |
9246 | 85.8k | sr.skip_line(); |
9247 | 85.8k | continue; |
9248 | 85.8k | } |
9249 | | |
9250 | | // object name |
9251 | 2.28M | if (sr.peek() == 'o' && (sr.peek_at(1) == ' ' || sr.peek_at(1) == '\t')) { |
9252 | | // flush previous face group. |
9253 | 361k | bool ret = exportGroupsToShape(&shape, prim_group, tags, material, name, |
9254 | 361k | triangulate, v, warn); |
9255 | 361k | (void)ret; |
9256 | | |
9257 | 361k | if (shape.mesh.indices.size() > 0 || shape.lines.indices.size() > 0 || |
9258 | 351k | shape.points.indices.size() > 0) { |
9259 | 191k | shapes->push_back(shape); |
9260 | 191k | } |
9261 | | |
9262 | | // material = -1; |
9263 | 361k | prim_group.clear(); |
9264 | 361k | shape = shape_t(); |
9265 | | |
9266 | 361k | sr.advance(2); |
9267 | 361k | std::string rest = sr.read_line(); |
9268 | 361k | name = rest; |
9269 | | |
9270 | 361k | sr.skip_line(); |
9271 | 361k | continue; |
9272 | 361k | } |
9273 | | |
9274 | 1.92M | if (sr.peek() == 't' && (sr.peek_at(1) == ' ' || sr.peek_at(1) == '\t')) { |
9275 | 903k | const int max_tag_nums = 8192; |
9276 | 903k | tag_t tag; |
9277 | | |
9278 | 903k | sr.advance(2); |
9279 | | |
9280 | 903k | tag.name = sr_parseString(sr); |
9281 | | |
9282 | 903k | tag_sizes ts = sr_parseTagTriple(sr); |
9283 | | |
9284 | 903k | if (ts.num_ints < 0) { |
9285 | 81.5k | ts.num_ints = 0; |
9286 | 81.5k | } |
9287 | 903k | if (ts.num_ints > max_tag_nums) { |
9288 | 409 | ts.num_ints = max_tag_nums; |
9289 | 409 | } |
9290 | | |
9291 | 903k | if (ts.num_reals < 0) { |
9292 | 13.5k | ts.num_reals = 0; |
9293 | 13.5k | } |
9294 | 903k | if (ts.num_reals > max_tag_nums) { |
9295 | 1.60k | ts.num_reals = max_tag_nums; |
9296 | 1.60k | } |
9297 | | |
9298 | 903k | if (ts.num_strings < 0) { |
9299 | 523 | ts.num_strings = 0; |
9300 | 523 | } |
9301 | 903k | if (ts.num_strings > max_tag_nums) { |
9302 | 747 | ts.num_strings = max_tag_nums; |
9303 | 747 | } |
9304 | | |
9305 | 903k | tag.intValues.resize(static_cast<size_t>(ts.num_ints)); |
9306 | | |
9307 | 7.09M | for (size_t i = 0; i < static_cast<size_t>(ts.num_ints); ++i) { |
9308 | 6.19M | tag.intValues[i] = sr_parseInt(sr); |
9309 | 6.19M | } |
9310 | | |
9311 | 903k | tag.floatValues.resize(static_cast<size_t>(ts.num_reals)); |
9312 | 14.8M | for (size_t i = 0; i < static_cast<size_t>(ts.num_reals); ++i) { |
9313 | 13.9M | tag.floatValues[i] = sr_parseReal(sr); |
9314 | 13.9M | } |
9315 | | |
9316 | 903k | tag.stringValues.resize(static_cast<size_t>(ts.num_strings)); |
9317 | 7.22M | for (size_t i = 0; i < static_cast<size_t>(ts.num_strings); ++i) { |
9318 | 6.32M | tag.stringValues[i] = sr_parseString(sr); |
9319 | 6.32M | } |
9320 | | |
9321 | 903k | tags.push_back(tag); |
9322 | | |
9323 | 903k | sr.skip_line(); |
9324 | 903k | continue; |
9325 | 903k | } |
9326 | | |
9327 | 1.02M | if (sr.peek() == 's' && (sr.peek_at(1) == ' ' || sr.peek_at(1) == '\t')) { |
9328 | | // smoothing group id |
9329 | 23.7k | sr.advance(2); |
9330 | 23.7k | sr.skip_space(); |
9331 | | |
9332 | 23.7k | if (sr.at_line_end()) { |
9333 | 4.72k | sr.skip_line(); |
9334 | 4.72k | continue; |
9335 | 4.72k | } |
9336 | | |
9337 | 19.0k | if (sr.peek() == '\r') { |
9338 | 0 | sr.skip_line(); |
9339 | 0 | continue; |
9340 | 0 | } |
9341 | | |
9342 | 19.0k | if (sr.remaining() >= 3 && sr.match("off", 3)) { |
9343 | 683 | current_smoothing_id = 0; |
9344 | 18.3k | } else { |
9345 | 18.3k | int smGroupId = sr_parseInt(sr); |
9346 | 18.3k | if (smGroupId < 0) { |
9347 | 1.97k | current_smoothing_id = 0; |
9348 | 16.3k | } else { |
9349 | 16.3k | current_smoothing_id = static_cast<unsigned int>(smGroupId); |
9350 | 16.3k | } |
9351 | 18.3k | } |
9352 | | |
9353 | 19.0k | sr.skip_line(); |
9354 | 19.0k | continue; |
9355 | 19.0k | } |
9356 | | |
9357 | | // Ignore unknown command. |
9358 | 999k | sr.skip_line(); |
9359 | 999k | } |
9360 | | |
9361 | | // not all vertices have colors, no default colors desired? -> clear colors |
9362 | 10.3k | if (!found_all_colors && !default_vcols_fallback) { |
9363 | 0 | vc.clear(); |
9364 | 0 | } |
9365 | | |
9366 | 10.3k | if (greatest_v_idx >= size_to_int(v.size() / 3)) { |
9367 | 1.06k | if (warn) { |
9368 | 1.06k | std::stringstream ss; |
9369 | 1.06k | ss << "Vertex indices out of bounds (line " << sr.line_num() << ".)\n\n"; |
9370 | 1.06k | (*warn) += ss.str(); |
9371 | 1.06k | } |
9372 | 1.06k | } |
9373 | 10.3k | if (greatest_vn_idx >= size_to_int(vn.size() / 3)) { |
9374 | 85 | if (warn) { |
9375 | 85 | std::stringstream ss; |
9376 | 85 | ss << "Vertex normal indices out of bounds (line " << sr.line_num() |
9377 | 85 | << ".)\n\n"; |
9378 | 85 | (*warn) += ss.str(); |
9379 | 85 | } |
9380 | 85 | } |
9381 | 10.3k | if (greatest_vt_idx >= size_to_int(vt.size() / 2)) { |
9382 | 151 | if (warn) { |
9383 | 151 | std::stringstream ss; |
9384 | 151 | ss << "Vertex texcoord indices out of bounds (line " << sr.line_num() |
9385 | 151 | << ".)\n\n"; |
9386 | 151 | (*warn) += ss.str(); |
9387 | 151 | } |
9388 | 151 | } |
9389 | | |
9390 | 10.3k | bool ret = exportGroupsToShape(&shape, prim_group, tags, material, name, |
9391 | 10.3k | triangulate, v, warn); |
9392 | 10.3k | if (ret || shape.mesh.indices.size()) { |
9393 | 1.76k | shapes->push_back(shape); |
9394 | 1.76k | } |
9395 | 10.3k | prim_group.clear(); |
9396 | | |
9397 | 10.3k | attrib->vertices.swap(v); |
9398 | 10.3k | attrib->vertex_weights.swap(vertex_weights); |
9399 | 10.3k | attrib->normals.swap(vn); |
9400 | 10.3k | attrib->texcoords.swap(vt); |
9401 | 10.3k | attrib->texcoord_ws.swap(vt_w); |
9402 | 10.3k | attrib->colors.swap(vc); |
9403 | 10.3k | attrib->skin_weights.swap(vw); |
9404 | | |
9405 | 10.3k | return true; |
9406 | 11.4k | } |
9407 | | |
9408 | | bool LoadObj(attrib_t *attrib, std::vector<shape_t> *shapes, |
9409 | | std::vector<material_t> *materials, std::string *warn, |
9410 | | std::string *err, const char *filename, const char *mtl_basedir, |
9411 | 0 | bool triangulate, bool default_vcols_fallback) { |
9412 | 0 | attrib->vertices.clear(); |
9413 | 0 | attrib->vertex_weights.clear(); |
9414 | 0 | attrib->normals.clear(); |
9415 | 0 | attrib->texcoords.clear(); |
9416 | 0 | attrib->texcoord_ws.clear(); |
9417 | 0 | attrib->colors.clear(); |
9418 | 0 | attrib->skin_weights.clear(); |
9419 | 0 | shapes->clear(); |
9420 | |
|
9421 | 0 | std::string baseDir = mtl_basedir ? mtl_basedir : ""; |
9422 | 0 | if (!baseDir.empty()) { |
9423 | 0 | #ifndef _WIN32 |
9424 | 0 | const char dirsep = '/'; |
9425 | | #else |
9426 | | const char dirsep = '\\'; |
9427 | | #endif |
9428 | 0 | if (baseDir[baseDir.length() - 1] != dirsep) baseDir += dirsep; |
9429 | 0 | } |
9430 | 0 | MaterialFileReader matFileReader(baseDir); |
9431 | |
|
9432 | | #ifdef TINYOBJLOADER_USE_MMAP |
9433 | | { |
9434 | | MappedFile mf; |
9435 | | if (!mf.open(filename)) { |
9436 | | if (err) { |
9437 | | std::stringstream ss; |
9438 | | ss << "Cannot open file [" << filename << "]\n"; |
9439 | | (*err) = ss.str(); |
9440 | | } |
9441 | | return false; |
9442 | | } |
9443 | | if (mf.size > TINYOBJLOADER_STREAM_READER_MAX_BYTES) { |
9444 | | if (err) { |
9445 | | std::stringstream ss; |
9446 | | ss << "input stream too large (" << mf.size |
9447 | | << " bytes exceeds limit " |
9448 | | << TINYOBJLOADER_STREAM_READER_MAX_BYTES << " bytes)\n"; |
9449 | | (*err) += ss.str(); |
9450 | | } |
9451 | | return false; |
9452 | | } |
9453 | | StreamReader sr(mf.data, mf.size); |
9454 | | return LoadObjInternal(attrib, shapes, materials, warn, err, sr, |
9455 | | &matFileReader, triangulate, default_vcols_fallback, |
9456 | | filename); |
9457 | | } |
9458 | | #else // !TINYOBJLOADER_USE_MMAP |
9459 | | #ifdef _WIN32 |
9460 | | std::ifstream ifs(LongPathW(UTF8ToWchar(filename)).c_str()); |
9461 | | #else |
9462 | 0 | std::ifstream ifs(filename); |
9463 | 0 | #endif |
9464 | 0 | if (!ifs) { |
9465 | 0 | if (err) { |
9466 | 0 | std::stringstream ss; |
9467 | 0 | ss << "Cannot open file [" << filename << "]\n"; |
9468 | 0 | (*err) = ss.str(); |
9469 | 0 | } |
9470 | 0 | return false; |
9471 | 0 | } |
9472 | 0 | { |
9473 | 0 | StreamReader sr(ifs); |
9474 | 0 | return LoadObjInternal(attrib, shapes, materials, warn, err, sr, |
9475 | 0 | &matFileReader, triangulate, default_vcols_fallback, |
9476 | 0 | filename); |
9477 | 0 | } |
9478 | 0 | #endif // TINYOBJLOADER_USE_MMAP |
9479 | 0 | } |
9480 | | |
9481 | | bool LoadObj(attrib_t *attrib, std::vector<shape_t> *shapes, |
9482 | | std::vector<material_t> *materials, std::string *warn, |
9483 | | std::string *err, std::istream *inStream, |
9484 | | MaterialReader *readMatFn /*= NULL*/, bool triangulate, |
9485 | 11.4k | bool default_vcols_fallback) { |
9486 | 11.4k | attrib->vertices.clear(); |
9487 | 11.4k | attrib->vertex_weights.clear(); |
9488 | 11.4k | attrib->normals.clear(); |
9489 | 11.4k | attrib->texcoords.clear(); |
9490 | 11.4k | attrib->texcoord_ws.clear(); |
9491 | 11.4k | attrib->colors.clear(); |
9492 | 11.4k | attrib->skin_weights.clear(); |
9493 | 11.4k | shapes->clear(); |
9494 | | |
9495 | 11.4k | StreamReader sr(*inStream); |
9496 | 11.4k | return LoadObjInternal(attrib, shapes, materials, warn, err, sr, |
9497 | 11.4k | readMatFn, triangulate, default_vcols_fallback); |
9498 | 11.4k | } |
9499 | | |
9500 | | |
9501 | | static bool LoadObjWithCallbackInternal(StreamReader &sr, |
9502 | | const callback_t &callback, |
9503 | | void *user_data, |
9504 | | MaterialReader *readMatFn, |
9505 | | std::string *warn, |
9506 | 0 | std::string *err) { |
9507 | 0 | if (sr.has_errors()) { |
9508 | 0 | if (err) { |
9509 | 0 | (*err) += sr.get_errors(); |
9510 | 0 | } |
9511 | 0 | return false; |
9512 | 0 | } |
9513 | | |
9514 | | // material |
9515 | 0 | std::set<std::string> material_filenames; |
9516 | 0 | std::map<std::string, int> material_map; |
9517 | 0 | int material_id = -1; |
9518 | |
|
9519 | 0 | std::vector<index_t> indices; |
9520 | 0 | std::vector<material_t> materials; |
9521 | 0 | std::vector<std::string> names; |
9522 | 0 | names.reserve(2); |
9523 | 0 | std::vector<const char *> names_out; |
9524 | | |
9525 | | // Handle BOM |
9526 | 0 | if (sr.remaining() >= 3 && |
9527 | 0 | static_cast<unsigned char>(sr.peek()) == 0xEF && |
9528 | 0 | static_cast<unsigned char>(sr.peek_at(1)) == 0xBB && |
9529 | 0 | static_cast<unsigned char>(sr.peek_at(2)) == 0xBF) { |
9530 | 0 | sr.advance(3); |
9531 | 0 | } |
9532 | |
|
9533 | 0 | while (!sr.eof()) { |
9534 | 0 | sr.skip_space(); |
9535 | 0 | if (sr.at_line_end()) { sr.skip_line(); continue; } |
9536 | 0 | if (sr.peek() == '#') { sr.skip_line(); continue; } |
9537 | | |
9538 | | // vertex |
9539 | 0 | if (sr.peek() == 'v' && (sr.peek_at(1) == ' ' || sr.peek_at(1) == '\t')) { |
9540 | 0 | sr.advance(2); |
9541 | 0 | real_t x, y, z; |
9542 | 0 | real_t r, g, b; |
9543 | |
|
9544 | 0 | int num_components = sr_parseVertexWithColor(&x, &y, &z, &r, &g, &b, sr, err, std::string()); |
9545 | 0 | if (num_components < 0) { |
9546 | 0 | return false; |
9547 | 0 | } |
9548 | 0 | if (callback.vertex_cb) { |
9549 | 0 | callback.vertex_cb(user_data, x, y, z, r); |
9550 | 0 | } |
9551 | 0 | if (callback.vertex_color_cb) { |
9552 | 0 | bool found_color = (num_components == 6); |
9553 | 0 | callback.vertex_color_cb(user_data, x, y, z, r, g, b, found_color); |
9554 | 0 | } |
9555 | 0 | sr.skip_line(); |
9556 | 0 | continue; |
9557 | 0 | } |
9558 | | |
9559 | | // normal |
9560 | 0 | if (sr.peek() == 'v' && sr.peek_at(1) == 'n' && (sr.peek_at(2) == ' ' || sr.peek_at(2) == '\t')) { |
9561 | 0 | sr.advance(3); |
9562 | 0 | real_t x, y, z; |
9563 | 0 | sr_parseReal3(&x, &y, &z, sr); |
9564 | 0 | if (callback.normal_cb) { |
9565 | 0 | callback.normal_cb(user_data, x, y, z); |
9566 | 0 | } |
9567 | 0 | sr.skip_line(); |
9568 | 0 | continue; |
9569 | 0 | } |
9570 | | |
9571 | | // texcoord |
9572 | 0 | if (sr.peek() == 'v' && sr.peek_at(1) == 't' && (sr.peek_at(2) == ' ' || sr.peek_at(2) == '\t')) { |
9573 | 0 | sr.advance(3); |
9574 | 0 | real_t x, y, z; |
9575 | 0 | sr_parseReal3(&x, &y, &z, sr); |
9576 | 0 | if (callback.texcoord_cb) { |
9577 | 0 | callback.texcoord_cb(user_data, x, y, z); |
9578 | 0 | } |
9579 | 0 | sr.skip_line(); |
9580 | 0 | continue; |
9581 | 0 | } |
9582 | | |
9583 | | // face |
9584 | 0 | if (sr.peek() == 'f' && (sr.peek_at(1) == ' ' || sr.peek_at(1) == '\t')) { |
9585 | 0 | sr.advance(2); |
9586 | 0 | sr.skip_space(); |
9587 | |
|
9588 | 0 | indices.clear(); |
9589 | 0 | size_t cf_loop_max = sr.remaining() + 1; |
9590 | 0 | size_t cf_loop_iter = 0; |
9591 | 0 | while (!sr.at_line_end() && sr.peek() != '#' && |
9592 | 0 | cf_loop_iter < cf_loop_max) { |
9593 | 0 | vertex_index_t vi = sr_parseRawTriple(sr); |
9594 | |
|
9595 | 0 | index_t idx; |
9596 | 0 | idx.vertex_index = vi.v_idx; |
9597 | 0 | idx.normal_index = vi.vn_idx; |
9598 | 0 | idx.texcoord_index = vi.vt_idx; |
9599 | |
|
9600 | 0 | indices.push_back(idx); |
9601 | 0 | sr.skip_space_and_cr(); |
9602 | 0 | cf_loop_iter++; |
9603 | 0 | } |
9604 | |
|
9605 | 0 | if (callback.index_cb && indices.size() > 0) { |
9606 | 0 | callback.index_cb(user_data, &indices.at(0), |
9607 | 0 | static_cast<int>(indices.size())); |
9608 | 0 | } |
9609 | |
|
9610 | 0 | sr.skip_line(); |
9611 | 0 | continue; |
9612 | 0 | } |
9613 | | |
9614 | | // use mtl |
9615 | 0 | if (sr.match("usemtl", 6) && (sr.peek_at(6) == ' ' || sr.peek_at(6) == '\t')) { |
9616 | 0 | sr.advance(6); |
9617 | 0 | std::string namebuf = sr_parseString(sr); |
9618 | |
|
9619 | 0 | int newMaterialId = -1; |
9620 | 0 | std::map<std::string, int>::const_iterator it = |
9621 | 0 | material_map.find(namebuf); |
9622 | 0 | if (it != material_map.end()) { |
9623 | 0 | newMaterialId = it->second; |
9624 | 0 | } else { |
9625 | 0 | if (warn && (!callback.usemtl_cb)) { |
9626 | 0 | (*warn) += "material [ " + namebuf + " ] not found in .mtl\n"; |
9627 | 0 | } |
9628 | 0 | } |
9629 | |
|
9630 | 0 | if (newMaterialId != material_id) { |
9631 | 0 | material_id = newMaterialId; |
9632 | 0 | } |
9633 | |
|
9634 | 0 | if (callback.usemtl_cb) { |
9635 | 0 | callback.usemtl_cb(user_data, namebuf.c_str(), material_id); |
9636 | 0 | } |
9637 | |
|
9638 | 0 | sr.skip_line(); |
9639 | 0 | continue; |
9640 | 0 | } |
9641 | | |
9642 | | // load mtl |
9643 | 0 | if (sr.match("mtllib", 6) && (sr.peek_at(6) == ' ' || sr.peek_at(6) == '\t')) { |
9644 | 0 | if (readMatFn) { |
9645 | 0 | sr.advance(7); |
9646 | |
|
9647 | 0 | std::string line_rest = trimTrailingWhitespace(sr.read_line()); |
9648 | 0 | std::vector<std::string> filenames; |
9649 | 0 | SplitString(line_rest, ' ', '\\', filenames); |
9650 | 0 | RemoveEmptyTokens(&filenames); |
9651 | |
|
9652 | 0 | if (filenames.empty()) { |
9653 | 0 | if (warn) { |
9654 | 0 | (*warn) += |
9655 | 0 | "Looks like empty filename for mtllib. Use default " |
9656 | 0 | "material. \n"; |
9657 | 0 | } |
9658 | 0 | } else { |
9659 | 0 | bool found = false; |
9660 | 0 | for (size_t s = 0; s < filenames.size(); s++) { |
9661 | 0 | if (material_filenames.count(filenames[s]) > 0) { |
9662 | 0 | found = true; |
9663 | 0 | continue; |
9664 | 0 | } |
9665 | | |
9666 | 0 | std::string warn_mtl; |
9667 | 0 | std::string err_mtl; |
9668 | 0 | bool ok = (*readMatFn)(filenames[s].c_str(), &materials, |
9669 | 0 | &material_map, &warn_mtl, &err_mtl); |
9670 | |
|
9671 | 0 | if (warn && (!warn_mtl.empty())) { |
9672 | 0 | (*warn) += warn_mtl; |
9673 | 0 | } |
9674 | |
|
9675 | 0 | if (err && (!err_mtl.empty())) { |
9676 | 0 | (*err) += err_mtl; |
9677 | 0 | } |
9678 | |
|
9679 | 0 | if (ok) { |
9680 | 0 | found = true; |
9681 | 0 | material_filenames.insert(filenames[s]); |
9682 | 0 | break; |
9683 | 0 | } |
9684 | 0 | } |
9685 | |
|
9686 | 0 | if (!found) { |
9687 | 0 | if (warn) { |
9688 | 0 | (*warn) += |
9689 | 0 | "Failed to load material file(s). Use default " |
9690 | 0 | "material.\n"; |
9691 | 0 | } |
9692 | 0 | } else { |
9693 | 0 | if (callback.mtllib_cb && !materials.empty()) { |
9694 | 0 | callback.mtllib_cb(user_data, &materials.at(0), |
9695 | 0 | static_cast<int>(materials.size())); |
9696 | 0 | } |
9697 | 0 | } |
9698 | 0 | } |
9699 | 0 | } |
9700 | |
|
9701 | 0 | sr.skip_line(); |
9702 | 0 | continue; |
9703 | 0 | } |
9704 | | |
9705 | | // group name |
9706 | 0 | if (sr.peek() == 'g' && (sr.peek_at(1) == ' ' || sr.peek_at(1) == '\t')) { |
9707 | 0 | names.clear(); |
9708 | |
|
9709 | 0 | size_t cg_loop_max = sr.remaining() + 1; |
9710 | 0 | size_t cg_loop_iter = 0; |
9711 | 0 | while (!sr.at_line_end() && sr.peek() != '#' && |
9712 | 0 | cg_loop_iter < cg_loop_max) { |
9713 | 0 | std::string str = sr_parseString(sr); |
9714 | 0 | names.push_back(str); |
9715 | 0 | sr.skip_space_and_cr(); |
9716 | 0 | cg_loop_iter++; |
9717 | 0 | } |
9718 | |
|
9719 | 0 | assert(names.size() > 0); |
9720 | | |
9721 | 0 | if (callback.group_cb) { |
9722 | 0 | if (names.size() > 1) { |
9723 | 0 | names_out.resize(names.size() - 1); |
9724 | 0 | for (size_t j = 0; j < names_out.size(); j++) { |
9725 | 0 | names_out[j] = names[j + 1].c_str(); |
9726 | 0 | } |
9727 | 0 | callback.group_cb(user_data, &names_out.at(0), |
9728 | 0 | static_cast<int>(names_out.size())); |
9729 | |
|
9730 | 0 | } else { |
9731 | 0 | callback.group_cb(user_data, NULL, 0); |
9732 | 0 | } |
9733 | 0 | } |
9734 | |
|
9735 | 0 | sr.skip_line(); |
9736 | 0 | continue; |
9737 | 0 | } |
9738 | | |
9739 | | // object name |
9740 | 0 | if (sr.peek() == 'o' && (sr.peek_at(1) == ' ' || sr.peek_at(1) == '\t')) { |
9741 | 0 | sr.advance(2); |
9742 | 0 | std::string object_name = sr.read_line(); |
9743 | |
|
9744 | 0 | if (callback.object_cb) { |
9745 | 0 | callback.object_cb(user_data, object_name.c_str()); |
9746 | 0 | } |
9747 | |
|
9748 | 0 | sr.skip_line(); |
9749 | 0 | continue; |
9750 | 0 | } |
9751 | | |
9752 | | #if 0 // @todo |
9753 | | if (sr.peek() == 't' && (sr.peek_at(1) == ' ' || sr.peek_at(1) == '\t')) { |
9754 | | tag_t tag; |
9755 | | |
9756 | | sr.advance(2); |
9757 | | tag.name = sr_parseString(sr); |
9758 | | |
9759 | | tag_sizes ts = sr_parseTagTriple(sr); |
9760 | | |
9761 | | tag.intValues.resize(static_cast<size_t>(ts.num_ints)); |
9762 | | |
9763 | | for (size_t i = 0; i < static_cast<size_t>(ts.num_ints); ++i) { |
9764 | | tag.intValues[i] = sr_parseInt(sr); |
9765 | | } |
9766 | | |
9767 | | tag.floatValues.resize(static_cast<size_t>(ts.num_reals)); |
9768 | | for (size_t i = 0; i < static_cast<size_t>(ts.num_reals); ++i) { |
9769 | | tag.floatValues[i] = sr_parseReal(sr); |
9770 | | } |
9771 | | |
9772 | | tag.stringValues.resize(static_cast<size_t>(ts.num_strings)); |
9773 | | for (size_t i = 0; i < static_cast<size_t>(ts.num_strings); ++i) { |
9774 | | tag.stringValues[i] = sr_parseString(sr); |
9775 | | } |
9776 | | |
9777 | | tags.push_back(tag); |
9778 | | } |
9779 | | #endif |
9780 | | |
9781 | | // Ignore unknown command. |
9782 | 0 | sr.skip_line(); |
9783 | 0 | } |
9784 | | |
9785 | 0 | return true; |
9786 | 0 | } |
9787 | | |
9788 | | bool LoadObjWithCallback(std::istream &inStream, const callback_t &callback, |
9789 | | void *user_data /*= NULL*/, |
9790 | | MaterialReader *readMatFn /*= NULL*/, |
9791 | | std::string *warn, /* = NULL*/ |
9792 | 0 | std::string *err /*= NULL*/) { |
9793 | 0 | StreamReader sr(inStream); |
9794 | 0 | return LoadObjWithCallbackInternal(sr, callback, user_data, readMatFn, |
9795 | 0 | warn, err); |
9796 | 0 | } |
9797 | | |
9798 | | bool ObjReader::ParseFromFile(const std::string &filename, |
9799 | 0 | const ObjReaderConfig &config) { |
9800 | 0 | std::string mtl_search_path; |
9801 | |
|
9802 | 0 | if (config.mtl_search_path.empty()) { |
9803 | | // |
9804 | | // split at last '/'(for unixish system) or '\\'(for windows) to get |
9805 | | // the base directory of .obj file |
9806 | | // |
9807 | 0 | size_t pos = filename.find_last_of("/\\"); |
9808 | 0 | if (pos != std::string::npos) { |
9809 | 0 | mtl_search_path = filename.substr(0, pos); |
9810 | 0 | } |
9811 | 0 | } else { |
9812 | 0 | mtl_search_path = config.mtl_search_path; |
9813 | 0 | } |
9814 | |
|
9815 | 0 | valid_ = LoadObj(&attrib_, &shapes_, &materials_, &warning_, &error_, |
9816 | 0 | filename.c_str(), mtl_search_path.c_str(), |
9817 | 0 | config.triangulate, config.vertex_color); |
9818 | |
|
9819 | 0 | return valid_; |
9820 | 0 | } |
9821 | | |
9822 | | bool ObjReader::ParseFromString(const std::string &obj_text, |
9823 | | const std::string &mtl_text, |
9824 | 11.4k | const ObjReaderConfig &config) { |
9825 | 11.4k | std::stringbuf obj_buf(obj_text); |
9826 | 11.4k | std::stringbuf mtl_buf(mtl_text); |
9827 | | |
9828 | 11.4k | std::istream obj_ifs(&obj_buf); |
9829 | 11.4k | std::istream mtl_ifs(&mtl_buf); |
9830 | | |
9831 | 11.4k | MaterialStreamReader mtl_ss(mtl_ifs); |
9832 | | |
9833 | 11.4k | valid_ = LoadObj(&attrib_, &shapes_, &materials_, &warning_, &error_, |
9834 | 11.4k | &obj_ifs, &mtl_ss, config.triangulate, config.vertex_color); |
9835 | | |
9836 | 11.4k | return valid_; |
9837 | 11.4k | } |
9838 | | |
9839 | | // =========================================================================== |
9840 | | // Optimized API implementation (C++11+) |
9841 | | // =========================================================================== |
9842 | | // ---- ArenaAllocator implementation ---- |
9843 | | |
9844 | 0 | void *ArenaAllocator::allocate(size_t bytes, size_t alignment) { |
9845 | 0 | if (bytes == 0) bytes = 1; |
9846 | | |
9847 | | // Try to allocate from current block |
9848 | 0 | if (head_) { |
9849 | 0 | size_t space = head_->capacity - head_->used; |
9850 | 0 | void *ptr = head_->data + head_->used; |
9851 | 0 | if (std::align(alignment, bytes, ptr, space)) { |
9852 | 0 | head_->used = static_cast<size_t>(static_cast<unsigned char *>(ptr) - |
9853 | 0 | head_->data) + |
9854 | 0 | bytes; |
9855 | 0 | return ptr; |
9856 | 0 | } |
9857 | 0 | } |
9858 | | |
9859 | | // Guard against size_t overflow in bytes + alignment |
9860 | 0 | if (bytes > SIZE_MAX - alignment) { |
9861 | | #ifdef TINYOBJLOADER_ENABLE_EXCEPTION |
9862 | | throw std::bad_alloc(); |
9863 | | #else |
9864 | 0 | return nullptr; |
9865 | 0 | #endif |
9866 | 0 | } |
9867 | | |
9868 | | // Need a new block |
9869 | 0 | Block *b = new_block(bytes + alignment); |
9870 | 0 | if (!b) return nullptr; |
9871 | 0 | size_t space = b->capacity; |
9872 | 0 | void *ptr = b->data; |
9873 | 0 | if (!std::align(alignment, bytes, ptr, space)) { |
9874 | | // Defensive guard: the block was allocated with capacity >= bytes + alignment, |
9875 | | // so alignment should always succeed. This handles edge cases where the |
9876 | | // capacity was insufficient due to unusual alignment requirements. |
9877 | | #ifdef TINYOBJLOADER_ENABLE_EXCEPTION |
9878 | | throw std::bad_alloc(); |
9879 | | #else |
9880 | 0 | return nullptr; |
9881 | 0 | #endif |
9882 | 0 | } |
9883 | 0 | b->used = |
9884 | 0 | static_cast<size_t>(static_cast<unsigned char *>(ptr) - b->data) + bytes; |
9885 | 0 | return ptr; |
9886 | 0 | } |
9887 | | |
9888 | 0 | void ArenaAllocator::reset() { destroy(); } |
9889 | | |
9890 | 0 | ArenaAllocator::Block *ArenaAllocator::new_block(size_t min_bytes) { |
9891 | 0 | size_t cap = (min_bytes > default_block_size_) ? min_bytes |
9892 | 0 | : default_block_size_; |
9893 | | #ifdef TINYOBJLOADER_ENABLE_EXCEPTION |
9894 | | // Allocate data buffer first: if this throws std::bad_alloc, no Block |
9895 | | // struct is leaked. If the subsequent Block allocation throws (very |
9896 | | // unlikely for a small POD), the data buffer is cleaned up. |
9897 | | unsigned char *data = new unsigned char[cap]; |
9898 | | Block *b; |
9899 | | try { |
9900 | | b = new Block; |
9901 | | } catch (...) { |
9902 | | delete[] data; |
9903 | | throw; |
9904 | | } |
9905 | | b->data = data; |
9906 | | #else |
9907 | 0 | Block *b = new (std::nothrow) Block; |
9908 | 0 | if (!b) return nullptr; |
9909 | 0 | b->data = new (std::nothrow) unsigned char[cap]; |
9910 | 0 | if (!b->data) { delete b; return nullptr; } |
9911 | 0 | #endif |
9912 | 0 | b->capacity = cap; |
9913 | 0 | b->used = 0; |
9914 | 0 | b->next = head_; |
9915 | 0 | head_ = b; |
9916 | 0 | return b; |
9917 | 0 | } |
9918 | | |
9919 | 0 | void ArenaAllocator::destroy() { |
9920 | 0 | Block *b = head_; |
9921 | 0 | while (b) { |
9922 | 0 | Block *next = b->next; |
9923 | 0 | delete[] b->data; |
9924 | 0 | delete b; |
9925 | 0 | b = next; |
9926 | 0 | } |
9927 | 0 | head_ = nullptr; |
9928 | 0 | } |
9929 | | |
9930 | | // ---- Optimized parser internals ---- |
9931 | | |
9932 | | namespace opt_internal { |
9933 | | |
9934 | | static const int kOptMaxThreads = 32; |
9935 | | |
9936 | | struct LineInfo { |
9937 | | size_t pos; |
9938 | | size_t len; |
9939 | | }; |
9940 | | |
9941 | 0 | #define TINYOBJ_OPT_IS_SPACE(x) (((x) == ' ') || ((x) == '\t')) |
9942 | | #define TINYOBJ_OPT_IS_DIGIT(x) \ |
9943 | 0 | (static_cast<unsigned int>((x) - '0') < static_cast<unsigned int>(10)) |
9944 | | #define TINYOBJ_OPT_IS_NEW_LINE(x) \ |
9945 | 0 | (((x) == '\r') || ((x) == '\n') || ((x) == '\0')) |
9946 | | |
9947 | 0 | static inline void opt_skip_space(const char **token) { |
9948 | 0 | while ((**token) == ' ' || (**token) == '\t') { |
9949 | 0 | (*token)++; |
9950 | 0 | } |
9951 | 0 | } |
9952 | | |
9953 | 0 | static inline int opt_until_space(const char *token) { |
9954 | 0 | const char *p = token; |
9955 | 0 | while (p[0] != '\0' && p[0] != ' ' && p[0] != '\t' && p[0] != '\r' && |
9956 | 0 | p[0] != '\n') { |
9957 | 0 | p++; |
9958 | 0 | } |
9959 | 0 | return static_cast<int>(p - token); |
9960 | 0 | } |
9961 | | |
9962 | 0 | static inline int opt_my_atoi(const char *c) { |
9963 | 0 | unsigned int value = 0; |
9964 | 0 | int sign = 1; |
9965 | 0 | if (*c == '+' || *c == '-') { |
9966 | 0 | if (*c == '-') sign = -1; |
9967 | 0 | c++; |
9968 | 0 | } |
9969 | 0 | while ((*c >= '0') && (*c <= '9')) { |
9970 | 0 | const unsigned int digit = static_cast<unsigned int>(*c - '0'); |
9971 | 0 | const unsigned int limit = (sign < 0) |
9972 | 0 | ? static_cast<unsigned int>(INT_MAX) + 1u |
9973 | 0 | : static_cast<unsigned int>(INT_MAX); |
9974 | 0 | if (value > (limit / 10u) || |
9975 | 0 | (value == (limit / 10u) && digit > (limit % 10u))) { |
9976 | 0 | return (sign < 0) ? INT_MIN : INT_MAX; |
9977 | 0 | } |
9978 | 0 | value = value * 10u + digit; |
9979 | 0 | c++; |
9980 | 0 | } |
9981 | 0 | if (sign < 0) { |
9982 | 0 | if (value == static_cast<unsigned int>(INT_MAX) + 1u) { |
9983 | 0 | return INT_MIN; |
9984 | 0 | } |
9985 | 0 | return -static_cast<int>(value); |
9986 | 0 | } |
9987 | 0 | return static_cast<int>(value); |
9988 | 0 | } |
9989 | | |
9990 | 0 | static inline const char *opt_find_index_token_end(const char *token) { |
9991 | 0 | const char *end = token; |
9992 | 0 | while (*end != '\0' && *end != '/' && *end != ' ' && *end != '\t' && |
9993 | 0 | *end != '\r' && *end != '\n') { |
9994 | 0 | end++; |
9995 | 0 | } |
9996 | 0 | return end; |
9997 | 0 | } |
9998 | | |
9999 | | static inline bool opt_tryParseIndexToken(const char *token, const char *end, |
10000 | 0 | int *value) { |
10001 | 0 | if (!value || !token || !end || token >= end) return false; |
10002 | | |
10003 | 0 | const char *cursor = token; |
10004 | 0 | if (*cursor == '+' || *cursor == '-') { |
10005 | 0 | cursor++; |
10006 | 0 | } |
10007 | 0 | if (cursor >= end) return false; |
10008 | 0 | while (cursor < end) { |
10009 | 0 | if (!TINYOBJ_OPT_IS_DIGIT(*cursor)) return false; |
10010 | 0 | cursor++; |
10011 | 0 | } |
10012 | | |
10013 | 0 | *value = opt_my_atoi(token); |
10014 | 0 | return true; |
10015 | 0 | } |
10016 | | |
10017 | | static inline bool opt_resolveIndexLikeLegacy(int idx, int n, int *ret, |
10018 | 0 | bool allow_zero) { |
10019 | 0 | if (!ret) return false; |
10020 | 0 | if (idx > 0) { |
10021 | 0 | (*ret) = idx - 1; |
10022 | 0 | return true; |
10023 | 0 | } |
10024 | 0 | if (idx == 0) { |
10025 | 0 | (*ret) = -1; |
10026 | 0 | return allow_zero; |
10027 | 0 | } |
10028 | | |
10029 | 0 | (*ret) = n + idx; |
10030 | 0 | return ((*ret) >= 0); |
10031 | 0 | } |
10032 | | |
10033 | | static inline void opt_appendZeroIndexWarning(std::string *warn, |
10034 | | const std::string &source_name, |
10035 | 0 | size_t line_num) { |
10036 | 0 | if (!warn) return; |
10037 | | |
10038 | 0 | std::stringstream ss; |
10039 | 0 | ss << source_name << ":" << line_num |
10040 | 0 | << ": warning: zero value index found (will have a value of -1 for " |
10041 | 0 | "normal and tex indices)\n"; |
10042 | 0 | (*warn) += ss.str(); |
10043 | 0 | } |
10044 | | |
10045 | | static inline bool opt_validateAndResolveFaceIndexLikeLegacy( |
10046 | | int raw_idx, int n, bool allow_zero, const std::string &source_name, |
10047 | 0 | size_t line_num, std::string *warn, int *resolved_idx) { |
10048 | 0 | if (raw_idx > 0) { |
10049 | 0 | if (resolved_idx) { |
10050 | 0 | (*resolved_idx) = raw_idx - 1; |
10051 | 0 | } |
10052 | 0 | return true; |
10053 | 0 | } |
10054 | | |
10055 | 0 | if (raw_idx == 0) { |
10056 | 0 | opt_appendZeroIndexWarning(warn, source_name, line_num); |
10057 | 0 | if (resolved_idx) { |
10058 | 0 | (*resolved_idx) = -1; |
10059 | 0 | } |
10060 | 0 | return allow_zero; |
10061 | 0 | } |
10062 | | |
10063 | 0 | if (resolved_idx) { |
10064 | 0 | (*resolved_idx) = n + raw_idx; |
10065 | 0 | return ((*resolved_idx) >= 0); |
10066 | 0 | } |
10067 | | |
10068 | 0 | return ((n + raw_idx) >= 0); |
10069 | 0 | } |
10070 | | |
10071 | 0 | static inline void opt_updateGreatestIndex(int idx, int *greatest) { |
10072 | 0 | if (!greatest) return; |
10073 | 0 | if (idx > *greatest) { |
10074 | 0 | *greatest = idx; |
10075 | 0 | } |
10076 | 0 | } |
10077 | | |
10078 | | static inline void opt_appendOutOfBoundsWarnings(std::string *warn, |
10079 | | int greatest_v_idx, |
10080 | | int greatest_vn_idx, |
10081 | | int greatest_vt_idx, |
10082 | | int num_vertices, |
10083 | | int num_normals, |
10084 | | int num_texcoords, |
10085 | 0 | size_t line_num) { |
10086 | 0 | if (!warn) return; |
10087 | | |
10088 | 0 | if (greatest_v_idx >= num_vertices) { |
10089 | 0 | std::stringstream ss; |
10090 | 0 | ss << "Vertex indices out of bounds (line " << line_num << ".)\n\n"; |
10091 | 0 | (*warn) += ss.str(); |
10092 | 0 | } |
10093 | 0 | if (greatest_vn_idx >= num_normals) { |
10094 | 0 | std::stringstream ss; |
10095 | 0 | ss << "Vertex normal indices out of bounds (line " << line_num << ".)\n\n"; |
10096 | 0 | (*warn) += ss.str(); |
10097 | 0 | } |
10098 | 0 | if (greatest_vt_idx >= num_texcoords) { |
10099 | 0 | std::stringstream ss; |
10100 | 0 | ss << "Vertex texcoord indices out of bounds (line " << line_num |
10101 | 0 | << ".)\n\n"; |
10102 | 0 | (*warn) += ss.str(); |
10103 | 0 | } |
10104 | 0 | } |
10105 | | |
10106 | | // Hand-written fallback double parser. Compiled only when fast_float is |
10107 | | // disabled (TINYOBJLOADER_DISABLE_FAST_FLOAT); otherwise callers use |
10108 | | // fast_float::from_chars directly and this function is not needed. |
10109 | | #ifdef TINYOBJLOADER_DISABLE_FAST_FLOAT |
10110 | | static bool opt_tryParseDouble(const char *s, const char *s_end, |
10111 | | double *result) { |
10112 | | if (s >= s_end) return false; |
10113 | | |
10114 | | // Handle nan/inf keywords with OBJ-compatible replacement values. |
10115 | | { |
10116 | | const char *p = s; |
10117 | | bool neg = false; |
10118 | | if (p < s_end && *p == '-') { neg = true; ++p; } |
10119 | | else if (p < s_end && *p == '+') { ++p; } |
10120 | | if (p < s_end) { |
10121 | | char fc = *p; |
10122 | | if (fc >= 'A' && fc <= 'Z') fc += 32; |
10123 | | if (fc == 'n' && (p + 2 < s_end)) { |
10124 | | char c1 = p[1], c2 = p[2]; |
10125 | | if (c1 >= 'A' && c1 <= 'Z') c1 += 32; |
10126 | | if (c2 >= 'A' && c2 <= 'Z') c2 += 32; |
10127 | | if (c1 == 'a' && c2 == 'n') { |
10128 | | *result = 0.0; |
10129 | | return true; |
10130 | | } |
10131 | | } |
10132 | | if (fc == 'i' && (p + 2 < s_end)) { |
10133 | | char c1 = p[1], c2 = p[2]; |
10134 | | if (c1 >= 'A' && c1 <= 'Z') c1 += 32; |
10135 | | if (c2 >= 'A' && c2 <= 'Z') c2 += 32; |
10136 | | if (c1 == 'n' && c2 == 'f') { |
10137 | | *result = neg ? std::numeric_limits<double>::lowest() |
10138 | | : (std::numeric_limits<double>::max)(); |
10139 | | return true; |
10140 | | } |
10141 | | } |
10142 | | } |
10143 | | } |
10144 | | |
10145 | | double mantissa = 0.0; |
10146 | | int exponent = 0; |
10147 | | char sign = '+'; |
10148 | | char exp_sign = '+'; |
10149 | | const char *curr = s; |
10150 | | int read = 0; |
10151 | | bool end_not_reached = false; |
10152 | | bool has_leading_decimal = false; |
10153 | | |
10154 | | if (*curr == '+' || *curr == '-') { |
10155 | | sign = *curr; |
10156 | | curr++; |
10157 | | } |
10158 | | |
10159 | | if (curr == s_end) return false; |
10160 | | |
10161 | | if (*curr == '.') { |
10162 | | has_leading_decimal = true; |
10163 | | } else if (!TINYOBJ_OPT_IS_DIGIT(*curr)) { |
10164 | | return false; |
10165 | | } |
10166 | | |
10167 | | end_not_reached = (curr != s_end); |
10168 | | if (!has_leading_decimal) { |
10169 | | while (end_not_reached && TINYOBJ_OPT_IS_DIGIT(*curr)) { |
10170 | | mantissa *= 10; |
10171 | | mantissa += static_cast<int>(*curr - '0'); |
10172 | | curr++; |
10173 | | read++; |
10174 | | end_not_reached = (curr != s_end); |
10175 | | } |
10176 | | if (read == 0) return false; |
10177 | | } |
10178 | | if (!end_not_reached) goto opt_assemble; |
10179 | | |
10180 | | if (*curr == '.') { |
10181 | | curr++; |
10182 | | end_not_reached = (curr != s_end); |
10183 | | double frac_scale = 0.1; |
10184 | | while (end_not_reached && TINYOBJ_OPT_IS_DIGIT(*curr)) { |
10185 | | mantissa += static_cast<int>(*curr - '0') * frac_scale; |
10186 | | frac_scale *= 0.1; |
10187 | | read++; |
10188 | | curr++; |
10189 | | end_not_reached = (curr != s_end); |
10190 | | } |
10191 | | if (has_leading_decimal && read == 0) return false; |
10192 | | } else if (*curr != 'e' && *curr != 'E') { |
10193 | | goto opt_assemble; |
10194 | | } |
10195 | | |
10196 | | if (!end_not_reached) goto opt_assemble; |
10197 | | |
10198 | | if (*curr == 'e' || *curr == 'E') { |
10199 | | curr++; |
10200 | | end_not_reached = (curr != s_end); |
10201 | | if (!end_not_reached) return false; |
10202 | | if (*curr == '+' || *curr == '-') { |
10203 | | exp_sign = *curr; |
10204 | | curr++; |
10205 | | end_not_reached = (curr != s_end); |
10206 | | } else if (!TINYOBJ_OPT_IS_DIGIT(*curr)) { |
10207 | | return false; |
10208 | | } |
10209 | | read = 0; |
10210 | | end_not_reached = (curr != s_end); |
10211 | | while (end_not_reached && TINYOBJ_OPT_IS_DIGIT(*curr)) { |
10212 | | // Clamp to avoid signed integer overflow (UB). |exponent| > 308 |
10213 | | // already exceeds double range, so further digits are irrelevant. |
10214 | | if (exponent < 0x7FFFFFF) { |
10215 | | exponent *= 10; |
10216 | | exponent += static_cast<int>(*curr - '0'); |
10217 | | } |
10218 | | curr++; |
10219 | | read++; |
10220 | | end_not_reached = (curr != s_end); |
10221 | | } |
10222 | | exponent *= (exp_sign == '+' ? 1 : -1); |
10223 | | if (read == 0) return false; |
10224 | | } |
10225 | | |
10226 | | opt_assemble: |
10227 | | *result = (sign == '+' ? 1.0 : -1.0) * |
10228 | | (exponent ? std::ldexp(mantissa * std::pow(5.0, exponent), exponent) |
10229 | | : mantissa); |
10230 | | return true; |
10231 | | } |
10232 | | #endif // TINYOBJLOADER_DISABLE_FAST_FLOAT |
10233 | | |
10234 | | struct opt_index_t { |
10235 | | int vertex_index, texcoord_index, normal_index; |
10236 | | // Sentinel for "field not present" to distinguish from OBJ relative index -1. |
10237 | | // Using expression form for C++11 static const initializer compatibility. |
10238 | | static const int kNotPresent = -2147483647 - 1; // == std::numeric_limits<int>::min() |
10239 | | opt_index_t() |
10240 | 0 | : vertex_index(kNotPresent), |
10241 | 0 | texcoord_index(kNotPresent), |
10242 | 0 | normal_index(kNotPresent) {} |
10243 | | opt_index_t(int vi, int ti, int ni) |
10244 | 0 | : vertex_index(vi), texcoord_index(ti), normal_index(ni) {} |
10245 | | }; |
10246 | | |
10247 | 0 | static bool opt_parseRawTriple(const char **token, opt_index_t *ret) { |
10248 | 0 | if (!token || !ret) return false; |
10249 | | |
10250 | 0 | opt_index_t vi; |
10251 | 0 | const char *segment_end = opt_find_index_token_end(*token); |
10252 | 0 | if (!opt_tryParseIndexToken(*token, segment_end, &vi.vertex_index)) { |
10253 | 0 | return false; |
10254 | 0 | } |
10255 | 0 | *token = segment_end; |
10256 | 0 | if (**token != '/') { |
10257 | 0 | *ret = vi; |
10258 | 0 | return true; |
10259 | 0 | } |
10260 | 0 | (*token)++; |
10261 | |
|
10262 | 0 | if (**token == '/') { |
10263 | 0 | (*token)++; |
10264 | 0 | segment_end = opt_find_index_token_end(*token); |
10265 | 0 | if (!opt_tryParseIndexToken(*token, segment_end, &vi.normal_index)) { |
10266 | 0 | return false; |
10267 | 0 | } |
10268 | 0 | *token = segment_end; |
10269 | 0 | *ret = vi; |
10270 | 0 | return true; |
10271 | 0 | } |
10272 | | |
10273 | 0 | segment_end = opt_find_index_token_end(*token); |
10274 | 0 | if (!opt_tryParseIndexToken(*token, segment_end, &vi.texcoord_index)) { |
10275 | 0 | return false; |
10276 | 0 | } |
10277 | 0 | *token = segment_end; |
10278 | 0 | if (**token != '/') { |
10279 | 0 | *ret = vi; |
10280 | 0 | return true; |
10281 | 0 | } |
10282 | 0 | (*token)++; |
10283 | 0 | segment_end = opt_find_index_token_end(*token); |
10284 | 0 | if (!opt_tryParseIndexToken(*token, segment_end, &vi.normal_index)) { |
10285 | 0 | return false; |
10286 | 0 | } |
10287 | 0 | *token = segment_end; |
10288 | 0 | *ret = vi; |
10289 | 0 | return true; |
10290 | 0 | } |
10291 | | |
10292 | 0 | static inline int opt_length_until_newline(const char *token, size_t n) { |
10293 | 0 | size_t len = 0; |
10294 | 0 | for (len = 0; len < n; len++) { |
10295 | 0 | if (token[len] == '\n' || token[len] == '\r') break; |
10296 | 0 | } |
10297 | | // Trim trailing whitespace |
10298 | 0 | while (len > 0 && (token[len - 1] == ' ' || token[len - 1] == '\t')) { |
10299 | 0 | len--; |
10300 | 0 | } |
10301 | 0 | return static_cast<int>(len); |
10302 | 0 | } |
10303 | | |
10304 | 0 | static inline int opt_length_until_token_or_comment(const char *token, size_t n) { |
10305 | 0 | size_t len = 0; |
10306 | 0 | for (len = 0; len < n; len++) { |
10307 | 0 | const char c = token[len]; |
10308 | 0 | if (c == '\n' || c == '\r' || c == ' ' || c == '\t') break; |
10309 | 0 | } |
10310 | 0 | return static_cast<int>(len); |
10311 | 0 | } |
10312 | | |
10313 | 0 | static inline std::string opt_parseGroupName(const char *token, size_t n) { |
10314 | 0 | std::string name; |
10315 | 0 | size_t i = 0; |
10316 | 0 | while (i < n) { |
10317 | 0 | while (i < n && (token[i] == ' ' || token[i] == '\t')) { |
10318 | 0 | i++; |
10319 | 0 | } |
10320 | 0 | if (i >= n || token[i] == '\n' || token[i] == '\r' || token[i] == '\0' || |
10321 | 0 | token[i] == '#') { |
10322 | 0 | break; |
10323 | 0 | } |
10324 | | |
10325 | 0 | const size_t start = i; |
10326 | 0 | while (i < n && token[i] != '\n' && token[i] != '\r' && |
10327 | 0 | token[i] != '\0' && token[i] != ' ' && token[i] != '\t') { |
10328 | 0 | i++; |
10329 | 0 | } |
10330 | |
|
10331 | 0 | if (!name.empty()) { |
10332 | 0 | name.push_back(' '); |
10333 | 0 | } |
10334 | 0 | name.append(token + start, i - start); |
10335 | 0 | } |
10336 | 0 | return name; |
10337 | 0 | } |
10338 | | |
10339 | 0 | static inline bool opt_tryParseFloatToken(real_t *out, const char **token) { |
10340 | 0 | if (!out || !token) return false; |
10341 | 0 | const char *cursor = *token; |
10342 | 0 | opt_skip_space(&cursor); |
10343 | 0 | if (TINYOBJ_OPT_IS_NEW_LINE(cursor[0]) || cursor[0] == '#' || cursor[0] == '\0') { |
10344 | 0 | return false; |
10345 | 0 | } |
10346 | 0 | const char *end = cursor; |
10347 | 0 | while (!TINYOBJ_OPT_IS_NEW_LINE(end[0]) && end[0] != '#' && end[0] != ' ' && |
10348 | 0 | end[0] != '\t' && end[0] != '\0') { |
10349 | 0 | end++; |
10350 | 0 | } |
10351 | 0 | #ifndef TINYOBJLOADER_DISABLE_FAST_FLOAT |
10352 | | // Handle nan/inf with OBJ-compatible values before fast_float. |
10353 | 0 | { |
10354 | 0 | const char *q = cursor; |
10355 | 0 | if (q < end && (*q == '+' || *q == '-')) ++q; |
10356 | 0 | if (q < end) { |
10357 | 0 | char fc = *q; |
10358 | 0 | if (fc >= 'A' && fc <= 'Z') fc += 32; |
10359 | 0 | if (fc == 'n' || fc == 'i') { |
10360 | 0 | double special_val; |
10361 | 0 | const char *end_ptr; |
10362 | 0 | if (detail_fp::tryParseNanInf(cursor, end, &special_val, &end_ptr)) { |
10363 | 0 | *out = static_cast<real_t>(special_val); |
10364 | 0 | *token = end; |
10365 | 0 | return true; |
10366 | 0 | } |
10367 | 0 | } |
10368 | 0 | } |
10369 | 0 | } |
10370 | | // Parse directly to real_t (float or double) via fast_float — avoids |
10371 | | // the double→float conversion and is ~3-4x faster than the hand-rolled parser. |
10372 | 0 | real_t tmp; |
10373 | 0 | auto r = fast_float::from_chars(cursor, end, tmp, |
10374 | 0 | fast_float::chars_format::general | |
10375 | 0 | fast_float::chars_format::allow_leading_plus); |
10376 | 0 | if (r.ec == tinyobj_ff::ff_errc::ok) { |
10377 | 0 | *out = tmp; |
10378 | 0 | *token = end; |
10379 | 0 | return true; |
10380 | 0 | } |
10381 | 0 | return false; |
10382 | | #else |
10383 | | double val = 0.0; |
10384 | | if (!opt_tryParseDouble(cursor, end, &val)) { |
10385 | | return false; |
10386 | | } |
10387 | | *out = static_cast<real_t>(val); |
10388 | | *token = end; |
10389 | | return true; |
10390 | | #endif |
10391 | 0 | } |
10392 | | |
10393 | 0 | static inline int opt_count_remaining_scalars(const char *token) { |
10394 | 0 | int count = 0; |
10395 | 0 | const char *cursor = token; |
10396 | 0 | while (true) { |
10397 | 0 | opt_skip_space(&cursor); |
10398 | 0 | if (TINYOBJ_OPT_IS_NEW_LINE(cursor[0]) || cursor[0] == '#' || |
10399 | 0 | cursor[0] == '\0') { |
10400 | 0 | break; |
10401 | 0 | } |
10402 | 0 | count++; |
10403 | 0 | while (!TINYOBJ_OPT_IS_NEW_LINE(cursor[0]) && cursor[0] != '#' && |
10404 | 0 | cursor[0] != ' ' && cursor[0] != '\t' && cursor[0] != '\0') { |
10405 | 0 | cursor++; |
10406 | 0 | } |
10407 | 0 | } |
10408 | 0 | return count; |
10409 | 0 | } |
10410 | | |
10411 | 0 | static inline bool opt_is_comment_start(const char *token) { |
10412 | 0 | return token[0] == '#'; |
10413 | 0 | } |
10414 | | |
10415 | | enum OptCommandType { |
10416 | | OPT_CMD_EMPTY, |
10417 | | OPT_CMD_V, |
10418 | | OPT_CMD_VN, |
10419 | | OPT_CMD_VT, |
10420 | | OPT_CMD_F, |
10421 | | OPT_CMD_G, |
10422 | | OPT_CMD_O, |
10423 | | OPT_CMD_USEMTL, |
10424 | | OPT_CMD_MTLLIB, |
10425 | | OPT_CMD_S |
10426 | | }; |
10427 | | |
10428 | | struct OptCommand { |
10429 | | static const unsigned int kInlineIndexCapacity = 24; |
10430 | | |
10431 | | real_t vx, vy, vz, vw; |
10432 | | real_t vc_r, vc_g, vc_b; |
10433 | | real_t nx, ny, nz; |
10434 | | real_t tx, ty, tw; |
10435 | | bool has_vertex_weight; |
10436 | | bool has_vertex_color; |
10437 | | bool has_texcoord_w; |
10438 | | |
10439 | | opt_index_t f_inline[kInlineIndexCapacity]; |
10440 | | unsigned int f_count; |
10441 | | unsigned int face_vertex_count; |
10442 | | unsigned int emitted_face_count; |
10443 | | unsigned int emitted_face_verts; |
10444 | | std::vector<opt_index_t> f_heap; |
10445 | | |
10446 | | const char *group_name; |
10447 | | unsigned int group_name_len; |
10448 | | std::string group_name_storage; |
10449 | | const char *object_name; |
10450 | | unsigned int object_name_len; |
10451 | | const char *material_name; |
10452 | | unsigned int material_name_len; |
10453 | | const char *mtllib_name; |
10454 | | unsigned int mtllib_name_len; |
10455 | | size_t source_line; |
10456 | | bool group_name_empty; |
10457 | | bool degenerate_face; |
10458 | | int resolved_material_id; |
10459 | | unsigned int smoothing_group_id; |
10460 | | |
10461 | | OptCommandType type; |
10462 | | |
10463 | | OptCommand() |
10464 | 0 | : vx(0), vy(0), vz(0), vw(1), |
10465 | 0 | vc_r(1), vc_g(1), vc_b(1), |
10466 | 0 | nx(0), ny(0), nz(0), |
10467 | 0 | tx(0), ty(0), tw(0), |
10468 | 0 | has_vertex_weight(false), has_vertex_color(false), |
10469 | 0 | has_texcoord_w(false), |
10470 | 0 | f_count(0), face_vertex_count(0), emitted_face_count(0), |
10471 | 0 | emitted_face_verts(0), |
10472 | 0 | group_name(nullptr), group_name_len(0), |
10473 | 0 | object_name(nullptr), object_name_len(0), |
10474 | 0 | material_name(nullptr), material_name_len(0), |
10475 | 0 | mtllib_name(nullptr), mtllib_name_len(0), |
10476 | 0 | source_line(0), group_name_empty(false), |
10477 | 0 | degenerate_face(false), |
10478 | 0 | resolved_material_id(-1), |
10479 | 0 | smoothing_group_id(0), |
10480 | 0 | type(OPT_CMD_EMPTY) {} |
10481 | | |
10482 | 0 | const opt_index_t *face_indices() const { |
10483 | 0 | return f_heap.empty() ? f_inline : f_heap.data(); |
10484 | 0 | } |
10485 | | }; |
10486 | | |
10487 | | struct OptCommandCount { |
10488 | | size_t num_v, num_vn, num_vt, num_f, num_indices; |
10489 | 0 | OptCommandCount() : num_v(0), num_vn(0), num_vt(0), num_f(0), num_indices(0) {} |
10490 | | }; |
10491 | | |
10492 | | // Compact face command — replaces OptCommand for 'f' lines |
10493 | | struct OptFaceCmd { |
10494 | | static const unsigned int kInlineCap = 4; |
10495 | | opt_index_t f_inline[kInlineCap]; // 48 bytes (covers tri + quad) |
10496 | | std::vector<opt_index_t> f_heap; // overflow for >4 vertex faces |
10497 | | uint32_t face_vertex_count; |
10498 | | uint32_t emitted_face_count; |
10499 | | uint32_t emitted_face_verts; |
10500 | | uint32_t f_count; |
10501 | | uint32_t source_line; |
10502 | | uint32_t v_count_before; // running v count when face was parsed (within thread) |
10503 | | uint32_t vn_count_before; |
10504 | | uint32_t vt_count_before; |
10505 | | bool degenerate; |
10506 | | |
10507 | 0 | const opt_index_t *face_indices() const { |
10508 | 0 | return f_heap.empty() ? f_inline : f_heap.data(); |
10509 | 0 | } |
10510 | | |
10511 | | OptFaceCmd() |
10512 | 0 | : face_vertex_count(0), emitted_face_count(0), emitted_face_verts(0), |
10513 | 0 | f_count(0), source_line(0), v_count_before(0), vn_count_before(0), |
10514 | 0 | vt_count_before(0), degenerate(false) {} |
10515 | | }; |
10516 | | |
10517 | | // Meta command — for g, o, usemtl, mtllib, s lines |
10518 | | struct OptMetaCmd { |
10519 | | OptCommandType type; |
10520 | | uint32_t source_line; |
10521 | | const char *str_ptr; |
10522 | | uint32_t str_len; |
10523 | | std::string str_storage; |
10524 | | bool group_name_empty; |
10525 | | int resolved_material_id; |
10526 | | uint32_t smoothing_group_id; |
10527 | | |
10528 | | OptMetaCmd() |
10529 | 0 | : type(OPT_CMD_EMPTY), source_line(0), str_ptr(NULL), str_len(0), |
10530 | 0 | group_name_empty(false), resolved_material_id(-1), |
10531 | 0 | smoothing_group_id(0) {} |
10532 | | }; |
10533 | | |
10534 | | // Sequence entry — ordering of faces and meta commands within a thread |
10535 | | struct OptSeqEntry { |
10536 | | enum Kind : unsigned char { SEQ_FACE = 0, SEQ_META = 1 }; |
10537 | | Kind kind; |
10538 | | uint32_t index; |
10539 | | }; |
10540 | | |
10541 | | // Per-thread parsed data — replaces vector<OptCommand> |
10542 | | struct OptThreadData { |
10543 | | // Vertex positions: 3 floats per vertex, contiguous |
10544 | | std::vector<real_t> v_pos; |
10545 | | // Vertex weights: 1 per vertex, lazily allocated (only if any has weight) |
10546 | | std::vector<real_t> v_weight; |
10547 | | // Vertex colors: 3 per vertex, lazily allocated |
10548 | | std::vector<real_t> v_color; |
10549 | | |
10550 | | // Normal data: 3 floats per normal |
10551 | | std::vector<real_t> vn_data; |
10552 | | |
10553 | | // Texcoord data: 2 floats per texcoord |
10554 | | std::vector<real_t> vt_data; |
10555 | | // Texcoord w: 1 per texcoord, lazily allocated |
10556 | | std::vector<real_t> vt_w; |
10557 | | |
10558 | | // Non-vertex commands |
10559 | | std::vector<OptFaceCmd> faces; |
10560 | | std::vector<OptMetaCmd> metas; |
10561 | | std::vector<OptSeqEntry> seq; |
10562 | | |
10563 | | // Counts |
10564 | | size_t num_v, num_vn, num_vt; |
10565 | | size_t num_f_indices; // total index_t entries for faces |
10566 | | size_t num_f_faces; // total emitted faces |
10567 | | |
10568 | | // Flags |
10569 | | bool saw_any_color, saw_missing_color; |
10570 | | bool saw_any_weight; |
10571 | | bool saw_any_texcoord_w; |
10572 | | bool saw_any_smoothing; |
10573 | | |
10574 | | // Error tracking |
10575 | | size_t error_line; |
10576 | | std::string error_message; |
10577 | | |
10578 | | OptThreadData() |
10579 | 0 | : num_v(0), num_vn(0), num_vt(0), num_f_indices(0), num_f_faces(0), |
10580 | 0 | saw_any_color(false), saw_missing_color(false), |
10581 | 0 | saw_any_weight(false), saw_any_texcoord_w(false), |
10582 | 0 | saw_any_smoothing(false), error_line(0) {} |
10583 | | }; |
10584 | | |
10585 | | static inline bool opt_is_valid_face_vertex(const std::vector<real_t> &vertices, |
10586 | 0 | const index_t &idx) { |
10587 | 0 | if (idx.vertex_index < 0) return false; |
10588 | 0 | const size_t vi = static_cast<size_t>(idx.vertex_index); |
10589 | 0 | return ((3 * vi + 2) < vertices.size()); |
10590 | 0 | } |
10591 | | |
10592 | | static inline size_t opt_triangulate_face(const std::vector<real_t> &vertices, |
10593 | | const index_t *face, |
10594 | | size_t face_count, |
10595 | 0 | index_t *dst) { |
10596 | 0 | if (face_count < 3) return 0; |
10597 | 0 | if (face_count == 3) { |
10598 | 0 | dst[0] = face[0]; |
10599 | 0 | dst[1] = face[1]; |
10600 | 0 | dst[2] = face[2]; |
10601 | 0 | return 3; |
10602 | 0 | } |
10603 | | |
10604 | 0 | for (size_t i = 0; i < face_count; i++) { |
10605 | 0 | if (!opt_is_valid_face_vertex(vertices, face[i])) { |
10606 | 0 | return 0; |
10607 | 0 | } |
10608 | 0 | } |
10609 | | |
10610 | 0 | if (face_count == 4) { |
10611 | 0 | const size_t vi0 = static_cast<size_t>(face[0].vertex_index); |
10612 | 0 | const size_t vi1 = static_cast<size_t>(face[1].vertex_index); |
10613 | 0 | const size_t vi2 = static_cast<size_t>(face[2].vertex_index); |
10614 | 0 | const size_t vi3 = static_cast<size_t>(face[3].vertex_index); |
10615 | |
|
10616 | 0 | const real_t v0x = vertices[vi0 * 3 + 0]; |
10617 | 0 | const real_t v0y = vertices[vi0 * 3 + 1]; |
10618 | 0 | const real_t v0z = vertices[vi0 * 3 + 2]; |
10619 | 0 | const real_t v1x = vertices[vi1 * 3 + 0]; |
10620 | 0 | const real_t v1y = vertices[vi1 * 3 + 1]; |
10621 | 0 | const real_t v1z = vertices[vi1 * 3 + 2]; |
10622 | 0 | const real_t v2x = vertices[vi2 * 3 + 0]; |
10623 | 0 | const real_t v2y = vertices[vi2 * 3 + 1]; |
10624 | 0 | const real_t v2z = vertices[vi2 * 3 + 2]; |
10625 | 0 | const real_t v3x = vertices[vi3 * 3 + 0]; |
10626 | 0 | const real_t v3y = vertices[vi3 * 3 + 1]; |
10627 | 0 | const real_t v3z = vertices[vi3 * 3 + 2]; |
10628 | |
|
10629 | 0 | const real_t e02x = v2x - v0x; |
10630 | 0 | const real_t e02y = v2y - v0y; |
10631 | 0 | const real_t e02z = v2z - v0z; |
10632 | 0 | const real_t e13x = v3x - v1x; |
10633 | 0 | const real_t e13y = v3y - v1y; |
10634 | 0 | const real_t e13z = v3z - v1z; |
10635 | 0 | const real_t sqr02 = e02x * e02x + e02y * e02y + e02z * e02z; |
10636 | 0 | const real_t sqr13 = e13x * e13x + e13y * e13y + e13z * e13z; |
10637 | |
|
10638 | 0 | if (sqr02 < sqr13) { |
10639 | 0 | dst[0] = face[0]; |
10640 | 0 | dst[1] = face[1]; |
10641 | 0 | dst[2] = face[2]; |
10642 | 0 | dst[3] = face[0]; |
10643 | 0 | dst[4] = face[2]; |
10644 | 0 | dst[5] = face[3]; |
10645 | 0 | } else { |
10646 | 0 | dst[0] = face[0]; |
10647 | 0 | dst[1] = face[1]; |
10648 | 0 | dst[2] = face[3]; |
10649 | 0 | dst[3] = face[1]; |
10650 | 0 | dst[4] = face[2]; |
10651 | 0 | dst[5] = face[3]; |
10652 | 0 | } |
10653 | 0 | return 6; |
10654 | 0 | } |
10655 | | |
10656 | 0 | std::vector<index_t> remaining(face, face + face_count); |
10657 | 0 | size_t axes[2] = {1, 2}; |
10658 | 0 | for (size_t k = 0; k < face_count; ++k) { |
10659 | 0 | const size_t vi0 = static_cast<size_t>(face[(k + 0) % face_count].vertex_index); |
10660 | 0 | const size_t vi1 = static_cast<size_t>(face[(k + 1) % face_count].vertex_index); |
10661 | 0 | const size_t vi2 = static_cast<size_t>(face[(k + 2) % face_count].vertex_index); |
10662 | 0 | const real_t v0x = vertices[vi0 * 3 + 0]; |
10663 | 0 | const real_t v0y = vertices[vi0 * 3 + 1]; |
10664 | 0 | const real_t v0z = vertices[vi0 * 3 + 2]; |
10665 | 0 | const real_t v1x = vertices[vi1 * 3 + 0]; |
10666 | 0 | const real_t v1y = vertices[vi1 * 3 + 1]; |
10667 | 0 | const real_t v1z = vertices[vi1 * 3 + 2]; |
10668 | 0 | const real_t v2x = vertices[vi2 * 3 + 0]; |
10669 | 0 | const real_t v2y = vertices[vi2 * 3 + 1]; |
10670 | 0 | const real_t v2z = vertices[vi2 * 3 + 2]; |
10671 | 0 | const real_t e0x = v1x - v0x; |
10672 | 0 | const real_t e0y = v1y - v0y; |
10673 | 0 | const real_t e0z = v1z - v0z; |
10674 | 0 | const real_t e1x = v2x - v1x; |
10675 | 0 | const real_t e1y = v2y - v1y; |
10676 | 0 | const real_t e1z = v2z - v1z; |
10677 | 0 | const real_t cx = std::fabs(e0y * e1z - e0z * e1y); |
10678 | 0 | const real_t cy = std::fabs(e0z * e1x - e0x * e1z); |
10679 | 0 | const real_t cz = std::fabs(e0x * e1y - e0y * e1x); |
10680 | 0 | const real_t epsilon = std::numeric_limits<real_t>::epsilon(); |
10681 | 0 | if (cx > epsilon || cy > epsilon || cz > epsilon) { |
10682 | 0 | if (!(cx > cy && cx > cz)) { |
10683 | 0 | axes[0] = 0; |
10684 | 0 | if (cz > cx && cz > cy) { |
10685 | 0 | axes[1] = 1; |
10686 | 0 | } |
10687 | 0 | } |
10688 | 0 | break; |
10689 | 0 | } |
10690 | 0 | } |
10691 | |
|
10692 | 0 | size_t out = 0; |
10693 | 0 | size_t guess_vert = 0; |
10694 | 0 | size_t remaining_iterations = remaining.size(); |
10695 | 0 | size_t previous_remaining_vertices = remaining.size(); |
10696 | 0 | while (remaining.size() > 3 && remaining_iterations > 0) { |
10697 | 0 | const size_t npolys = remaining.size(); |
10698 | 0 | if (guess_vert >= npolys) { |
10699 | 0 | guess_vert -= npolys; |
10700 | 0 | } |
10701 | |
|
10702 | 0 | if (previous_remaining_vertices != npolys) { |
10703 | 0 | previous_remaining_vertices = npolys; |
10704 | 0 | remaining_iterations = npolys; |
10705 | 0 | } else { |
10706 | 0 | remaining_iterations--; |
10707 | 0 | } |
10708 | |
|
10709 | 0 | index_t ind[3]; |
10710 | 0 | real_t vx[3]; |
10711 | 0 | real_t vy[3]; |
10712 | 0 | for (size_t k = 0; k < 3; k++) { |
10713 | 0 | ind[k] = remaining[(guess_vert + k) % npolys]; |
10714 | 0 | const size_t vi = static_cast<size_t>(ind[k].vertex_index); |
10715 | 0 | vx[k] = vertices[vi * 3 + axes[0]]; |
10716 | 0 | vy[k] = vertices[vi * 3 + axes[1]]; |
10717 | 0 | } |
10718 | |
|
10719 | 0 | const real_t e0x = vx[1] - vx[0]; |
10720 | 0 | const real_t e0y = vy[1] - vy[0]; |
10721 | 0 | const real_t e1x = vx[2] - vx[1]; |
10722 | 0 | const real_t e1y = vy[2] - vy[1]; |
10723 | 0 | const real_t cross_val = e0x * e1y - e0y * e1x; |
10724 | 0 | const real_t area = |
10725 | 0 | (vx[0] * vy[1] - vy[0] * vx[1]) * static_cast<real_t>(0.5); |
10726 | 0 | if (cross_val * area < static_cast<real_t>(0.0)) { |
10727 | 0 | guess_vert += 1; |
10728 | 0 | continue; |
10729 | 0 | } |
10730 | | |
10731 | 0 | bool overlap = false; |
10732 | 0 | for (size_t other_vert = 3; other_vert < npolys; ++other_vert) { |
10733 | 0 | const size_t idx = (guess_vert + other_vert) % npolys; |
10734 | 0 | const size_t ovi = static_cast<size_t>(remaining[idx].vertex_index); |
10735 | 0 | const real_t tx = vertices[ovi * 3 + axes[0]]; |
10736 | 0 | const real_t ty = vertices[ovi * 3 + axes[1]]; |
10737 | 0 | if (pnpoly(3, vx, vy, tx, ty)) { |
10738 | 0 | overlap = true; |
10739 | 0 | break; |
10740 | 0 | } |
10741 | 0 | } |
10742 | |
|
10743 | 0 | if (overlap) { |
10744 | 0 | guess_vert += 1; |
10745 | 0 | continue; |
10746 | 0 | } |
10747 | | |
10748 | 0 | dst[out++] = ind[0]; |
10749 | 0 | dst[out++] = ind[1]; |
10750 | 0 | dst[out++] = ind[2]; |
10751 | 0 | remaining.erase(remaining.begin() + |
10752 | 0 | static_cast<std::ptrdiff_t>((guess_vert + 1) % npolys)); |
10753 | 0 | } |
10754 | |
|
10755 | 0 | if (remaining.size() == 3) { |
10756 | 0 | dst[out++] = remaining[0]; |
10757 | 0 | dst[out++] = remaining[1]; |
10758 | 0 | dst[out++] = remaining[2]; |
10759 | 0 | } |
10760 | |
|
10761 | 0 | return out; |
10762 | 0 | } |
10763 | | |
10764 | | // Pointer+size overloads for TypedArray-based API |
10765 | | static inline bool opt_is_valid_face_vertex(const real_t * /*vertices*/, |
10766 | | size_t vertices_size, |
10767 | 0 | const index_t &idx) { |
10768 | 0 | if (idx.vertex_index < 0) return false; |
10769 | 0 | const size_t vi = static_cast<size_t>(idx.vertex_index); |
10770 | 0 | return ((3 * vi + 2) < vertices_size); |
10771 | 0 | } |
10772 | | |
10773 | | static inline size_t opt_triangulate_face(const real_t *vertices, |
10774 | | size_t vertices_size, |
10775 | | const index_t *face, |
10776 | | size_t face_count, |
10777 | 0 | index_t *dst) { |
10778 | 0 | if (face_count < 3) return 0; |
10779 | 0 | if (face_count == 3) { |
10780 | 0 | dst[0] = face[0]; dst[1] = face[1]; dst[2] = face[2]; |
10781 | 0 | return 3; |
10782 | 0 | } |
10783 | | |
10784 | 0 | for (size_t i = 0; i < face_count; i++) { |
10785 | 0 | if (!opt_is_valid_face_vertex(vertices, vertices_size, face[i])) return 0; |
10786 | 0 | } |
10787 | | |
10788 | 0 | if (face_count == 4) { |
10789 | 0 | const size_t vi0 = static_cast<size_t>(face[0].vertex_index); |
10790 | 0 | const size_t vi1 = static_cast<size_t>(face[1].vertex_index); |
10791 | 0 | const size_t vi2 = static_cast<size_t>(face[2].vertex_index); |
10792 | 0 | const size_t vi3 = static_cast<size_t>(face[3].vertex_index); |
10793 | 0 | const real_t e02x = vertices[vi2*3+0] - vertices[vi0*3+0]; |
10794 | 0 | const real_t e02y = vertices[vi2*3+1] - vertices[vi0*3+1]; |
10795 | 0 | const real_t e02z = vertices[vi2*3+2] - vertices[vi0*3+2]; |
10796 | 0 | const real_t e13x = vertices[vi3*3+0] - vertices[vi1*3+0]; |
10797 | 0 | const real_t e13y = vertices[vi3*3+1] - vertices[vi1*3+1]; |
10798 | 0 | const real_t e13z = vertices[vi3*3+2] - vertices[vi1*3+2]; |
10799 | 0 | const real_t sqr02 = e02x*e02x + e02y*e02y + e02z*e02z; |
10800 | 0 | const real_t sqr13 = e13x*e13x + e13y*e13y + e13z*e13z; |
10801 | 0 | if (sqr02 < sqr13) { |
10802 | 0 | dst[0]=face[0]; dst[1]=face[1]; dst[2]=face[2]; |
10803 | 0 | dst[3]=face[0]; dst[4]=face[2]; dst[5]=face[3]; |
10804 | 0 | } else { |
10805 | 0 | dst[0]=face[0]; dst[1]=face[1]; dst[2]=face[3]; |
10806 | 0 | dst[3]=face[1]; dst[4]=face[2]; dst[5]=face[3]; |
10807 | 0 | } |
10808 | 0 | return 6; |
10809 | 0 | } |
10810 | | |
10811 | | // General polygon — ear clipping |
10812 | 0 | std::vector<index_t> remaining(face, face + face_count); |
10813 | 0 | size_t axes[2] = {1, 2}; |
10814 | 0 | for (size_t k = 0; k < face_count; ++k) { |
10815 | 0 | const size_t vi0 = static_cast<size_t>(face[(k+0)%face_count].vertex_index); |
10816 | 0 | const size_t vi1 = static_cast<size_t>(face[(k+1)%face_count].vertex_index); |
10817 | 0 | const size_t vi2 = static_cast<size_t>(face[(k+2)%face_count].vertex_index); |
10818 | 0 | const real_t e0x = vertices[vi1*3+0]-vertices[vi0*3+0]; |
10819 | 0 | const real_t e0y = vertices[vi1*3+1]-vertices[vi0*3+1]; |
10820 | 0 | const real_t e0z = vertices[vi1*3+2]-vertices[vi0*3+2]; |
10821 | 0 | const real_t e1x = vertices[vi2*3+0]-vertices[vi1*3+0]; |
10822 | 0 | const real_t e1y = vertices[vi2*3+1]-vertices[vi1*3+1]; |
10823 | 0 | const real_t e1z = vertices[vi2*3+2]-vertices[vi1*3+2]; |
10824 | 0 | const real_t cx = std::fabs(e0y*e1z - e0z*e1y); |
10825 | 0 | const real_t cy = std::fabs(e0z*e1x - e0x*e1z); |
10826 | 0 | const real_t cz = std::fabs(e0x*e1y - e0y*e1x); |
10827 | 0 | const real_t epsilon = std::numeric_limits<real_t>::epsilon(); |
10828 | 0 | if (cx > epsilon || cy > epsilon || cz > epsilon) { |
10829 | 0 | if (!(cx > cy && cx > cz)) { |
10830 | 0 | axes[0] = 0; |
10831 | 0 | if (cz > cx && cz > cy) axes[1] = 1; |
10832 | 0 | } |
10833 | 0 | break; |
10834 | 0 | } |
10835 | 0 | } |
10836 | |
|
10837 | 0 | size_t out = 0; |
10838 | 0 | size_t guess_vert = 0; |
10839 | 0 | size_t remaining_iterations = remaining.size(); |
10840 | 0 | size_t previous_remaining_vertices = remaining.size(); |
10841 | 0 | while (remaining.size() > 3 && remaining_iterations > 0) { |
10842 | 0 | const size_t npolys = remaining.size(); |
10843 | 0 | if (guess_vert >= npolys) guess_vert -= npolys; |
10844 | 0 | if (previous_remaining_vertices != npolys) { |
10845 | 0 | previous_remaining_vertices = npolys; |
10846 | 0 | remaining_iterations = npolys; |
10847 | 0 | } else { |
10848 | 0 | remaining_iterations--; |
10849 | 0 | } |
10850 | 0 | index_t ind[3]; |
10851 | 0 | real_t vx[3], vy[3]; |
10852 | 0 | for (size_t k = 0; k < 3; k++) { |
10853 | 0 | ind[k] = remaining[(guess_vert + k) % npolys]; |
10854 | 0 | const size_t vi = static_cast<size_t>(ind[k].vertex_index); |
10855 | 0 | vx[k] = vertices[vi*3+axes[0]]; |
10856 | 0 | vy[k] = vertices[vi*3+axes[1]]; |
10857 | 0 | } |
10858 | 0 | const real_t e0x_ = vx[1]-vx[0], e0y_ = vy[1]-vy[0]; |
10859 | 0 | const real_t e1x_ = vx[2]-vx[1], e1y_ = vy[2]-vy[1]; |
10860 | 0 | const real_t cross_val = e0x_*e1y_ - e0y_*e1x_; |
10861 | 0 | const real_t area = (vx[0]*vy[1] - vy[0]*vx[1]) * static_cast<real_t>(0.5); |
10862 | 0 | if (cross_val * area < static_cast<real_t>(0.0)) { |
10863 | 0 | guess_vert += 1; continue; |
10864 | 0 | } |
10865 | 0 | bool overlap = false; |
10866 | 0 | for (size_t other_vert = 3; other_vert < npolys; ++other_vert) { |
10867 | 0 | const size_t idx = (guess_vert + other_vert) % npolys; |
10868 | 0 | const size_t ovi = static_cast<size_t>(remaining[idx].vertex_index); |
10869 | 0 | const real_t ptx = vertices[ovi*3+axes[0]]; |
10870 | 0 | const real_t pty = vertices[ovi*3+axes[1]]; |
10871 | 0 | if (pnpoly(3, vx, vy, ptx, pty)) { overlap = true; break; } |
10872 | 0 | } |
10873 | 0 | if (overlap) { guess_vert += 1; continue; } |
10874 | 0 | dst[out++] = ind[0]; dst[out++] = ind[1]; dst[out++] = ind[2]; |
10875 | 0 | remaining.erase(remaining.begin() + |
10876 | 0 | static_cast<std::ptrdiff_t>((guess_vert + 1) % npolys)); |
10877 | 0 | } |
10878 | 0 | if (remaining.size() == 3) { |
10879 | 0 | dst[out++] = remaining[0]; dst[out++] = remaining[1]; dst[out++] = remaining[2]; |
10880 | 0 | } |
10881 | 0 | return out; |
10882 | 0 | } |
10883 | | |
10884 | | static bool opt_parseLine(OptCommand *command, const char *p, size_t p_len, |
10885 | | bool triangulate, bool *parse_error, |
10886 | 0 | std::string *parse_error_message) { |
10887 | | // Parse directly from the original buffer without copying. |
10888 | | // The caller guarantees that p[p_len] is '\n' (or a sentinel), |
10889 | | // so character-scanning helpers that stop on '\n' are safe. |
10890 | 0 | if (parse_error) *parse_error = false; |
10891 | 0 | if (parse_error_message) parse_error_message->clear(); |
10892 | 0 | const char *token = p; |
10893 | 0 | command->type = OPT_CMD_EMPTY; |
10894 | 0 | opt_skip_space(&token); |
10895 | |
|
10896 | 0 | if (TINYOBJ_OPT_IS_NEW_LINE(token[0]) || token[0] == '#') return false; |
10897 | | |
10898 | | // vertex |
10899 | 0 | if (token[0] == 'v' && TINYOBJ_OPT_IS_SPACE(token[1])) { |
10900 | 0 | token += 2; |
10901 | 0 | real_t x = 0, y = 0, z = 0; |
10902 | 0 | real_t r = real_t(1.0), g = real_t(1.0), b = real_t(1.0); |
10903 | 0 | real_t w = real_t(1.0); |
10904 | 0 | if (!opt_tryParseFloatToken(&x, &token) || |
10905 | 0 | !opt_tryParseFloatToken(&y, &token) || |
10906 | 0 | !opt_tryParseFloatToken(&z, &token)) { |
10907 | 0 | if (parse_error) *parse_error = true; |
10908 | 0 | if (parse_error_message) *parse_error_message = "failed to parse `v' line"; |
10909 | 0 | return false; |
10910 | 0 | } |
10911 | 0 | const char *extra_token = token; |
10912 | 0 | opt_skip_space(&extra_token); |
10913 | 0 | const int extra_components = opt_count_remaining_scalars(extra_token); |
10914 | 0 | if (extra_components == 1) { |
10915 | | // v x y z w (4 total) — weight only |
10916 | 0 | const char *weight_cursor = extra_token; |
10917 | 0 | if (opt_tryParseFloatToken(&w, &weight_cursor)) { |
10918 | 0 | command->has_vertex_weight = true; |
10919 | 0 | command->vw = w; |
10920 | 0 | } |
10921 | 0 | } else if (extra_components == 3) { |
10922 | | // v x y z r g b (6 total) — color, weight = r (legacy compat) |
10923 | 0 | real_t cr = r, cg = g, cb = b; |
10924 | 0 | const char *color_cursor = extra_token; |
10925 | 0 | if (opt_tryParseFloatToken(&cr, &color_cursor) && |
10926 | 0 | opt_tryParseFloatToken(&cg, &color_cursor) && |
10927 | 0 | opt_tryParseFloatToken(&cb, &color_cursor)) { |
10928 | 0 | command->has_vertex_weight = true; |
10929 | 0 | command->vw = cr; |
10930 | 0 | command->has_vertex_color = true; |
10931 | 0 | command->vc_r = cr; |
10932 | 0 | command->vc_g = cg; |
10933 | 0 | command->vc_b = cb; |
10934 | 0 | } |
10935 | 0 | } else if (extra_components >= 4) { |
10936 | | // v x y z w r g b (7+ total) — weight + color. |
10937 | | // NOTE: 7-component vertices are NOT part of the OBJ spec nor the common |
10938 | | // vertex-color extension, which define only xyz / xyzw / xyzrgb and treat |
10939 | | // weight and color as mutually exclusive. tinyobjloader accepts this as |
10940 | | // an extension: the 4th value is the weight, the next three are RGB. |
10941 | | // (The classic LoadObj / LoadObjWithCallback path instead caps at 6 and |
10942 | | // ignores any extra tokens, so it differs from LoadObjOpt here.) |
10943 | 0 | real_t vw = real_t(1.0), cr = r, cg = g, cb = b; |
10944 | 0 | const char *wc_cursor = extra_token; |
10945 | 0 | if (opt_tryParseFloatToken(&vw, &wc_cursor) && |
10946 | 0 | opt_tryParseFloatToken(&cr, &wc_cursor) && |
10947 | 0 | opt_tryParseFloatToken(&cg, &wc_cursor) && |
10948 | 0 | opt_tryParseFloatToken(&cb, &wc_cursor)) { |
10949 | 0 | command->has_vertex_weight = true; |
10950 | 0 | command->vw = vw; |
10951 | 0 | command->has_vertex_color = true; |
10952 | 0 | command->vc_r = cr; |
10953 | 0 | command->vc_g = cg; |
10954 | 0 | command->vc_b = cb; |
10955 | 0 | } |
10956 | 0 | } |
10957 | 0 | command->vx = x; |
10958 | 0 | command->vy = y; |
10959 | 0 | command->vz = z; |
10960 | 0 | command->type = OPT_CMD_V; |
10961 | 0 | return true; |
10962 | 0 | } |
10963 | | |
10964 | | // normal |
10965 | 0 | if (token[0] == 'v' && token[1] == 'n' && TINYOBJ_OPT_IS_SPACE(token[2])) { |
10966 | 0 | token += 3; |
10967 | 0 | real_t x = 0, y = 0, z = 0; |
10968 | 0 | if (!opt_tryParseFloatToken(&x, &token) || |
10969 | 0 | !opt_tryParseFloatToken(&y, &token) || |
10970 | 0 | !opt_tryParseFloatToken(&z, &token)) { |
10971 | 0 | if (parse_error) *parse_error = true; |
10972 | 0 | if (parse_error_message) *parse_error_message = "failed to parse `vn' line"; |
10973 | 0 | return false; |
10974 | 0 | } |
10975 | 0 | command->nx = x; |
10976 | 0 | command->ny = y; |
10977 | 0 | command->nz = z; |
10978 | 0 | command->type = OPT_CMD_VN; |
10979 | 0 | return true; |
10980 | 0 | } |
10981 | | |
10982 | | // texcoord |
10983 | 0 | if (token[0] == 'v' && token[1] == 't' && TINYOBJ_OPT_IS_SPACE(token[2])) { |
10984 | 0 | token += 3; |
10985 | 0 | real_t x = 0, y = 0, w = 0; |
10986 | 0 | if (!opt_tryParseFloatToken(&x, &token)) { |
10987 | 0 | if (parse_error) *parse_error = true; |
10988 | 0 | if (parse_error_message) *parse_error_message = "failed to parse `vt' line"; |
10989 | 0 | return false; |
10990 | 0 | } |
10991 | 0 | const char *y_token = token; |
10992 | 0 | if (opt_tryParseFloatToken(&y, &y_token)) { |
10993 | 0 | token = y_token; |
10994 | 0 | } |
10995 | 0 | const char *extra_token = token; |
10996 | 0 | opt_skip_space(&extra_token); |
10997 | 0 | if (!TINYOBJ_OPT_IS_NEW_LINE(extra_token[0]) && extra_token[0] != '#' && |
10998 | 0 | opt_tryParseFloatToken(&w, &extra_token)) { |
10999 | 0 | command->has_texcoord_w = true; |
11000 | 0 | command->tw = w; |
11001 | 0 | } |
11002 | 0 | command->tx = x; |
11003 | 0 | command->ty = y; |
11004 | 0 | command->type = OPT_CMD_VT; |
11005 | 0 | return true; |
11006 | 0 | } |
11007 | | |
11008 | | // face |
11009 | 0 | if (token[0] == 'f' && TINYOBJ_OPT_IS_SPACE(token[1])) { |
11010 | 0 | token += 2; |
11011 | 0 | opt_skip_space(&token); |
11012 | | |
11013 | | // Collect face vertices (use small stack buffer for typical case) |
11014 | 0 | opt_index_t face_buf[8]; |
11015 | 0 | std::vector<opt_index_t> face_overflow; |
11016 | 0 | int face_count = 0; |
11017 | |
|
11018 | 0 | while (!TINYOBJ_OPT_IS_NEW_LINE(token[0]) && !opt_is_comment_start(token)) { |
11019 | 0 | opt_index_t vi; |
11020 | 0 | if (!opt_parseRawTriple(&token, &vi)) { |
11021 | 0 | if (parse_error) *parse_error = true; |
11022 | 0 | if (parse_error_message) { |
11023 | 0 | *parse_error_message = "failed to parse `f' line (invalid vertex index)"; |
11024 | 0 | } |
11025 | 0 | return false; |
11026 | 0 | } |
11027 | 0 | opt_skip_space(&token); |
11028 | 0 | if (face_count < 8) { |
11029 | 0 | face_buf[face_count++] = vi; |
11030 | 0 | } else { |
11031 | 0 | if (face_count == 8) { |
11032 | 0 | face_overflow.reserve(16); |
11033 | 0 | for (int k = 0; k < 8; k++) face_overflow.push_back(face_buf[k]); |
11034 | 0 | } |
11035 | 0 | face_overflow.push_back(vi); |
11036 | 0 | face_count++; |
11037 | 0 | } |
11038 | 0 | } |
11039 | | |
11040 | 0 | command->type = OPT_CMD_F; |
11041 | 0 | command->face_vertex_count = static_cast<unsigned int>(face_count); |
11042 | | |
11043 | | // Preserve degenerate faces so warnings and EOF shape behavior can match |
11044 | | // the legacy loader, but do not reserve output slots for them. |
11045 | 0 | if (face_count < 3) { |
11046 | 0 | command->degenerate_face = true; |
11047 | 0 | command->emitted_face_count = 0; |
11048 | 0 | command->emitted_face_verts = 0; |
11049 | 0 | command->f_count = 0; |
11050 | 0 | return true; |
11051 | 0 | } |
11052 | | |
11053 | 0 | if (triangulate) { |
11054 | 0 | command->emitted_face_count = static_cast<unsigned int>(face_count - 2); |
11055 | 0 | command->emitted_face_verts = 3; |
11056 | 0 | command->f_count = command->emitted_face_count * 3; |
11057 | |
|
11058 | 0 | opt_index_t *dst = command->f_inline; |
11059 | 0 | if (command->face_vertex_count > OptCommand::kInlineIndexCapacity) { |
11060 | 0 | command->f_heap.resize(command->face_vertex_count); |
11061 | 0 | dst = command->f_heap.data(); |
11062 | 0 | } |
11063 | 0 | if (face_count <= 8) { |
11064 | 0 | for (int k = 0; k < face_count; k++) dst[k] = face_buf[k]; |
11065 | 0 | } else { |
11066 | 0 | for (size_t k = 0; k < face_overflow.size(); k++) dst[k] = face_overflow[k]; |
11067 | 0 | } |
11068 | 0 | } else { |
11069 | 0 | command->emitted_face_count = 1; |
11070 | 0 | command->emitted_face_verts = static_cast<unsigned int>(face_count); |
11071 | 0 | command->f_count = static_cast<unsigned int>(face_count); |
11072 | 0 | command->face_vertex_count = static_cast<unsigned int>(face_count); |
11073 | |
|
11074 | 0 | opt_index_t *dst = command->f_inline; |
11075 | 0 | if (command->f_count > OptCommand::kInlineIndexCapacity) { |
11076 | 0 | command->f_heap.resize(command->f_count); |
11077 | 0 | dst = command->f_heap.data(); |
11078 | 0 | } |
11079 | |
|
11080 | 0 | if (face_count <= 8) { |
11081 | 0 | for (int k = 0; k < face_count; k++) dst[k] = face_buf[k]; |
11082 | 0 | } else { |
11083 | 0 | for (size_t k = 0; k < face_overflow.size(); k++) { |
11084 | 0 | dst[k] = face_overflow[k]; |
11085 | 0 | } |
11086 | 0 | } |
11087 | 0 | } |
11088 | 0 | return true; |
11089 | 0 | } |
11090 | | |
11091 | | // usemtl |
11092 | 0 | if (std::strncmp(token, "usemtl", 6) == 0 && |
11093 | 0 | TINYOBJ_OPT_IS_SPACE(token[6])) { |
11094 | 0 | token += 7; |
11095 | 0 | opt_skip_space(&token); |
11096 | 0 | command->material_name = token; |
11097 | 0 | command->material_name_len = static_cast<unsigned int>( |
11098 | 0 | opt_length_until_token_or_comment(token, |
11099 | 0 | p_len - static_cast<size_t>(token - p))); |
11100 | 0 | command->type = OPT_CMD_USEMTL; |
11101 | 0 | return true; |
11102 | 0 | } |
11103 | | |
11104 | | // mtllib |
11105 | 0 | if (std::strncmp(token, "mtllib", 6) == 0 && |
11106 | 0 | TINYOBJ_OPT_IS_SPACE(token[6])) { |
11107 | 0 | token += 7; |
11108 | 0 | opt_skip_space(&token); |
11109 | 0 | command->mtllib_name = token; |
11110 | 0 | command->mtllib_name_len = static_cast<unsigned int>( |
11111 | 0 | opt_length_until_newline(token, |
11112 | 0 | p_len - static_cast<size_t>(token - p))); |
11113 | 0 | command->type = OPT_CMD_MTLLIB; |
11114 | 0 | return true; |
11115 | 0 | } |
11116 | | |
11117 | | // group |
11118 | 0 | if (token[0] == 'g' && |
11119 | 0 | (TINYOBJ_OPT_IS_SPACE(token[1]) || TINYOBJ_OPT_IS_NEW_LINE(token[1]) || |
11120 | 0 | token[1] == '\0')) { |
11121 | 0 | if (TINYOBJ_OPT_IS_SPACE(token[1])) { |
11122 | 0 | token += 2; |
11123 | 0 | } else { |
11124 | 0 | token += 1; |
11125 | 0 | } |
11126 | 0 | command->group_name_storage = opt_parseGroupName( |
11127 | 0 | token, p_len - static_cast<size_t>(token - p)); |
11128 | 0 | command->group_name = nullptr; |
11129 | 0 | command->group_name_len = |
11130 | 0 | static_cast<unsigned int>(command->group_name_storage.size()); |
11131 | 0 | command->group_name_empty = command->group_name_storage.empty(); |
11132 | 0 | command->type = OPT_CMD_G; |
11133 | 0 | return true; |
11134 | 0 | } |
11135 | | |
11136 | | // object |
11137 | 0 | if (token[0] == 'o' && TINYOBJ_OPT_IS_SPACE(token[1])) { |
11138 | 0 | token += 2; |
11139 | 0 | command->object_name = token; |
11140 | 0 | command->object_name_len = static_cast<unsigned int>( |
11141 | 0 | p_len - static_cast<size_t>(token - p)); |
11142 | 0 | command->type = OPT_CMD_O; |
11143 | 0 | return true; |
11144 | 0 | } |
11145 | | |
11146 | | // smoothing group |
11147 | 0 | if (token[0] == 's' && TINYOBJ_OPT_IS_SPACE(token[1])) { |
11148 | 0 | token += 2; |
11149 | 0 | opt_skip_space(&token); |
11150 | 0 | if (TINYOBJ_OPT_IS_NEW_LINE(token[0])) { |
11151 | 0 | command->smoothing_group_id = 0; |
11152 | 0 | } else if (token[0] == 'o' && token[1] == 'f' && token[2] == 'f' && |
11153 | 0 | (TINYOBJ_OPT_IS_NEW_LINE(token[3]) || token[3] == '\0' || |
11154 | 0 | token[3] == ' ' || token[3] == '\t')) { |
11155 | 0 | command->smoothing_group_id = 0; |
11156 | 0 | } else { |
11157 | 0 | const int sm = opt_my_atoi(token); |
11158 | 0 | command->smoothing_group_id = (sm > 0) ? static_cast<unsigned int>(sm) : 0; |
11159 | 0 | } |
11160 | 0 | command->type = OPT_CMD_S; |
11161 | 0 | return true; |
11162 | 0 | } |
11163 | | |
11164 | 0 | return false; |
11165 | 0 | } |
11166 | | |
11167 | | // ---- SIMD newline scanning ---- |
11168 | | |
11169 | | #ifdef TINYOBJLOADER_USE_SIMD |
11170 | | |
11171 | | // Include <intrin.h> for _BitScanForward on MSVC. Placed here (not at the top) |
11172 | | // because it's only needed when TINYOBJLOADER_USE_SIMD is enabled. |
11173 | | #if defined(_MSC_VER) |
11174 | | #include <intrin.h> |
11175 | | #endif |
11176 | | |
11177 | | // Portable count-trailing-zeros for SIMD bitmask extraction |
11178 | | static inline unsigned int tinyobj_ctz(unsigned int x) { |
11179 | | #if defined(_MSC_VER) |
11180 | | unsigned long idx; |
11181 | | _BitScanForward(&idx, x); |
11182 | | return static_cast<unsigned int>(idx); |
11183 | | #else |
11184 | | return static_cast<unsigned int>(__builtin_ctz(x)); |
11185 | | #endif |
11186 | | } |
11187 | | |
11188 | | #if defined(TINYOBJLOADER_SIMD_AVX2) |
11189 | | |
11190 | | /// AVX2-accelerated line-ending scanning — finds '\n' and '\r' positions. |
11191 | | static void simd_find_newlines(const char *buf, size_t len, |
11192 | | std::vector<size_t> &positions) { |
11193 | | const __m256i nl = _mm256_set1_epi8('\n'); |
11194 | | const __m256i cr = _mm256_set1_epi8('\r'); |
11195 | | size_t i = 0; |
11196 | | for (; i + 32 <= len; i += 32) { |
11197 | | __m256i chunk = |
11198 | | _mm256_loadu_si256(reinterpret_cast<const __m256i *>(buf + i)); |
11199 | | __m256i cmp_nl = _mm256_cmpeq_epi8(chunk, nl); |
11200 | | __m256i cmp_cr = _mm256_cmpeq_epi8(chunk, cr); |
11201 | | __m256i combined = _mm256_or_si256(cmp_nl, cmp_cr); |
11202 | | unsigned int mask = static_cast<unsigned int>(_mm256_movemask_epi8(combined)); |
11203 | | while (mask) { |
11204 | | unsigned int bit = tinyobj_ctz(mask); |
11205 | | positions.push_back(i + bit); |
11206 | | mask &= mask - 1; |
11207 | | } |
11208 | | } |
11209 | | // Scalar tail |
11210 | | for (; i < len; i++) { |
11211 | | if (buf[i] == '\n' || buf[i] == '\r') positions.push_back(i); |
11212 | | } |
11213 | | } |
11214 | | |
11215 | | #elif defined(TINYOBJLOADER_SIMD_SSE2) |
11216 | | |
11217 | | /// SSE2-accelerated line-ending scanning — finds '\n' and '\r' positions. |
11218 | | static void simd_find_newlines(const char *buf, size_t len, |
11219 | | std::vector<size_t> &positions) { |
11220 | | const __m128i nl = _mm_set1_epi8('\n'); |
11221 | | const __m128i cr = _mm_set1_epi8('\r'); |
11222 | | size_t i = 0; |
11223 | | for (; i + 16 <= len; i += 16) { |
11224 | | __m128i chunk = |
11225 | | _mm_loadu_si128(reinterpret_cast<const __m128i *>(buf + i)); |
11226 | | __m128i cmp_nl = _mm_cmpeq_epi8(chunk, nl); |
11227 | | __m128i cmp_cr = _mm_cmpeq_epi8(chunk, cr); |
11228 | | __m128i combined = _mm_or_si128(cmp_nl, cmp_cr); |
11229 | | int mask = _mm_movemask_epi8(combined); |
11230 | | while (mask) { |
11231 | | int bit = static_cast<int>(tinyobj_ctz(static_cast<unsigned int>(mask))); |
11232 | | positions.push_back(i + static_cast<size_t>(bit)); |
11233 | | mask &= mask - 1; |
11234 | | } |
11235 | | } |
11236 | | // Scalar tail |
11237 | | for (; i < len; i++) { |
11238 | | if (buf[i] == '\n' || buf[i] == '\r') positions.push_back(i); |
11239 | | } |
11240 | | } |
11241 | | |
11242 | | #elif defined(TINYOBJLOADER_SIMD_NEON) |
11243 | | |
11244 | | /// NEON-accelerated line-ending scanning — finds '\n' and '\r' positions. |
11245 | | static void simd_find_newlines(const char *buf, size_t len, |
11246 | | std::vector<size_t> &positions) { |
11247 | | const uint8x16_t nl = vdupq_n_u8('\n'); |
11248 | | const uint8x16_t cr = vdupq_n_u8('\r'); |
11249 | | size_t i = 0; |
11250 | | for (; i + 16 <= len; i += 16) { |
11251 | | uint8x16_t chunk = vld1q_u8(reinterpret_cast<const uint8_t *>(buf + i)); |
11252 | | uint8x16_t cmp_nl = vceqq_u8(chunk, nl); |
11253 | | uint8x16_t cmp_cr = vceqq_u8(chunk, cr); |
11254 | | uint8x16_t combined = vorrq_u8(cmp_nl, cmp_cr); |
11255 | | for (int j = 0; j < 16; j++) { |
11256 | | if (vgetq_lane_u8(combined, 0) != 0) { |
11257 | | positions.push_back(i + static_cast<size_t>(j)); |
11258 | | } |
11259 | | combined = vextq_u8(combined, combined, 1); |
11260 | | } |
11261 | | } |
11262 | | for (; i < len; i++) { |
11263 | | if (buf[i] == '\n' || buf[i] == '\r') positions.push_back(i); |
11264 | | } |
11265 | | } |
11266 | | |
11267 | | #endif // SIMD variant |
11268 | | |
11269 | | /// Build LineInfo array from SIMD-detected line-ending positions. |
11270 | | /// The positions array may contain both '\r' and '\n' hits; |
11271 | | /// consecutive \r\n pairs are collapsed into a single line boundary. |
11272 | | static void simd_build_line_infos(const char *buf, size_t len, |
11273 | | const std::vector<size_t> &nl_positions, |
11274 | | std::vector<LineInfo> &out) { |
11275 | | out.reserve(nl_positions.size() + 1); |
11276 | | size_t prev = 0; |
11277 | | for (size_t k = 0; k < nl_positions.size(); k++) { |
11278 | | size_t pos = nl_positions[k]; |
11279 | | // Skip the '\n' in a '\r\n' pair (the '\r' already created a boundary) |
11280 | | if (buf[pos] == '\n' && pos > 0 && buf[pos - 1] == '\r') { |
11281 | | prev = pos + 1; |
11282 | | continue; |
11283 | | } |
11284 | | size_t line_len = pos - prev; |
11285 | | if (line_len > 0) { |
11286 | | LineInfo info; |
11287 | | info.pos = prev; |
11288 | | info.len = line_len; |
11289 | | out.push_back(info); |
11290 | | } |
11291 | | prev = pos + 1; |
11292 | | } |
11293 | | // Handle last line without trailing newline |
11294 | | if (prev < len) { |
11295 | | size_t line_len = len - prev; |
11296 | | if (line_len > 0) { |
11297 | | LineInfo info; |
11298 | | info.pos = prev; |
11299 | | info.len = line_len; |
11300 | | out.push_back(info); |
11301 | | } |
11302 | | } |
11303 | | } |
11304 | | |
11305 | | #endif // TINYOBJLOADER_USE_SIMD |
11306 | | |
11307 | | /// Scalar fallback newline scanning — handles \n, \r\n, and bare \r |
11308 | | static void scalar_find_line_infos(const char *buf, size_t start, size_t end, |
11309 | 0 | std::vector<LineInfo> &out) { |
11310 | 0 | size_t prev = start; |
11311 | 0 | for (size_t i = start; i < end; i++) { |
11312 | 0 | if (buf[i] == '\n' || buf[i] == '\r') { |
11313 | 0 | size_t line_len = i - prev; |
11314 | | // Skip \r in \r\n pair |
11315 | 0 | if (buf[i] == '\r' && (i + 1 < end) && buf[i + 1] == '\n') { |
11316 | 0 | if (line_len > 0) { |
11317 | 0 | LineInfo info; |
11318 | 0 | info.pos = prev; |
11319 | 0 | info.len = line_len; |
11320 | 0 | out.push_back(info); |
11321 | 0 | } |
11322 | 0 | i++; // skip the \n in \r\n |
11323 | 0 | } else { |
11324 | | // bare \n or bare \r |
11325 | 0 | if (line_len > 0) { |
11326 | 0 | LineInfo info; |
11327 | 0 | info.pos = prev; |
11328 | 0 | info.len = line_len; |
11329 | 0 | out.push_back(info); |
11330 | 0 | } |
11331 | 0 | } |
11332 | 0 | prev = i + 1; |
11333 | 0 | } |
11334 | 0 | } |
11335 | 0 | if (prev < end) { |
11336 | 0 | size_t line_len = end - prev; |
11337 | 0 | if (line_len > 0) { |
11338 | 0 | LineInfo info; |
11339 | 0 | info.pos = prev; |
11340 | 0 | info.len = line_len; |
11341 | 0 | out.push_back(info); |
11342 | 0 | } |
11343 | 0 | } |
11344 | 0 | } |
11345 | | |
11346 | | template <typename DstVec, typename SrcVec> |
11347 | 0 | static void opt_assign_vector(DstVec *dst, const SrcVec &src) { |
11348 | 0 | if (!dst) return; |
11349 | 0 | dst->assign(src.begin(), src.end()); |
11350 | 0 | } Unexecuted instantiation: fuzz_ParseFromString.cc:void tinyobj::opt_internal::opt_assign_vector<std::__1::vector<float, std::__1::allocator<float> >, std::__1::vector<float, std::__1::allocator<float> > >(std::__1::vector<float, std::__1::allocator<float> >*, std::__1::vector<float, std::__1::allocator<float> > const&) Unexecuted instantiation: fuzz_ParseFromString.cc:void tinyobj::opt_internal::opt_assign_vector<std::__1::vector<tinyobj::index_t, std::__1::allocator<tinyobj::index_t> >, std::__1::vector<tinyobj::index_t, std::__1::allocator<tinyobj::index_t> > >(std::__1::vector<tinyobj::index_t, std::__1::allocator<tinyobj::index_t> >*, std::__1::vector<tinyobj::index_t, std::__1::allocator<tinyobj::index_t> > const&) Unexecuted instantiation: fuzz_ParseFromString.cc:void tinyobj::opt_internal::opt_assign_vector<std::__1::vector<unsigned int, std::__1::allocator<unsigned int> >, std::__1::vector<unsigned int, std::__1::allocator<unsigned int> > >(std::__1::vector<unsigned int, std::__1::allocator<unsigned int> >*, std::__1::vector<unsigned int, std::__1::allocator<unsigned int> > const&) Unexecuted instantiation: fuzz_ParseFromString.cc:void tinyobj::opt_internal::opt_assign_vector<std::__1::vector<int, std::__1::allocator<int> >, std::__1::vector<int, std::__1::allocator<int> > >(std::__1::vector<int, std::__1::allocator<int> >*, std::__1::vector<int, std::__1::allocator<int> > const&) |
11351 | | |
11352 | 0 | static void ConvertLegacyShapeToBasic(const shape_t &src, basic_shape_t<> *dst) { |
11353 | 0 | if (!dst) return; |
11354 | 0 | dst->name = src.name; |
11355 | 0 | opt_assign_vector(&dst->mesh.indices, src.mesh.indices); |
11356 | 0 | opt_assign_vector(&dst->mesh.num_face_vertices, src.mesh.num_face_vertices); |
11357 | 0 | opt_assign_vector(&dst->mesh.material_ids, src.mesh.material_ids); |
11358 | 0 | opt_assign_vector(&dst->mesh.smoothing_group_ids, |
11359 | 0 | src.mesh.smoothing_group_ids); |
11360 | 0 | dst->mesh.tags = src.mesh.tags; |
11361 | 0 | opt_assign_vector(&dst->lines.indices, src.lines.indices); |
11362 | 0 | opt_assign_vector(&dst->lines.num_line_vertices, src.lines.num_line_vertices); |
11363 | 0 | opt_assign_vector(&dst->points.indices, src.points.indices); |
11364 | 0 | } |
11365 | | |
11366 | | static void ConvertLegacyResultToBasic( |
11367 | | const attrib_t &src_attrib, const std::vector<shape_t> &src_shapes, |
11368 | | const std::vector<material_t> &src_materials, basic_attrib_t<> *dst_attrib, |
11369 | | std::vector<basic_shape_t<> > *dst_shapes, |
11370 | 0 | std::vector<material_t> *dst_materials) { |
11371 | 0 | if (dst_attrib) { |
11372 | 0 | opt_assign_vector(&dst_attrib->vertices, src_attrib.vertices); |
11373 | 0 | opt_assign_vector(&dst_attrib->vertex_weights, src_attrib.vertex_weights); |
11374 | 0 | opt_assign_vector(&dst_attrib->normals, src_attrib.normals); |
11375 | 0 | opt_assign_vector(&dst_attrib->texcoords, src_attrib.texcoords); |
11376 | 0 | opt_assign_vector(&dst_attrib->texcoord_ws, src_attrib.texcoord_ws); |
11377 | 0 | opt_assign_vector(&dst_attrib->colors, src_attrib.colors); |
11378 | 0 | dst_attrib->skin_weights = src_attrib.skin_weights; |
11379 | 0 | dst_attrib->indices.clear(); |
11380 | 0 | dst_attrib->face_num_verts.clear(); |
11381 | 0 | dst_attrib->material_ids.clear(); |
11382 | 0 | for (size_t si = 0; si < src_shapes.size(); si++) { |
11383 | 0 | const mesh_t &mesh = src_shapes[si].mesh; |
11384 | 0 | dst_attrib->indices.insert(dst_attrib->indices.end(), mesh.indices.begin(), |
11385 | 0 | mesh.indices.end()); |
11386 | 0 | dst_attrib->face_num_verts.insert(dst_attrib->face_num_verts.end(), |
11387 | 0 | mesh.num_face_vertices.begin(), |
11388 | 0 | mesh.num_face_vertices.end()); |
11389 | 0 | dst_attrib->material_ids.insert(dst_attrib->material_ids.end(), |
11390 | 0 | mesh.material_ids.begin(), |
11391 | 0 | mesh.material_ids.end()); |
11392 | 0 | } |
11393 | 0 | } |
11394 | |
|
11395 | 0 | if (dst_shapes) { |
11396 | 0 | dst_shapes->clear(); |
11397 | 0 | dst_shapes->resize(src_shapes.size()); |
11398 | 0 | for (size_t i = 0; i < src_shapes.size(); i++) { |
11399 | 0 | ConvertLegacyShapeToBasic(src_shapes[i], &(*dst_shapes)[i]); |
11400 | 0 | } |
11401 | 0 | } |
11402 | |
|
11403 | 0 | if (dst_materials) { |
11404 | 0 | (*dst_materials) = src_materials; |
11405 | 0 | } |
11406 | 0 | } |
11407 | | |
11408 | | /// Check if the first non-whitespace token on a line (starting at p, length |
11409 | | /// rem) requires the legacy parser. |
11410 | 0 | static inline bool opt_line_start_is_legacy(const char *p, size_t rem) { |
11411 | 0 | while (rem > 0 && (*p == ' ' || *p == '\t')) { p++; rem--; } |
11412 | 0 | if (rem >= 3 && p[0] == 'v' && p[1] == 'w' && |
11413 | 0 | (p[2] == ' ' || p[2] == '\t')) |
11414 | 0 | return true; |
11415 | 0 | if (rem >= 2 && |
11416 | 0 | ((p[0] == 'l' || p[0] == 'p' || p[0] == 't') && |
11417 | 0 | (p[1] == ' ' || p[1] == '\t'))) |
11418 | 0 | return true; |
11419 | 0 | return false; |
11420 | 0 | } |
11421 | | |
11422 | | /// Fast whole-buffer scan for legacy-only tokens (vw, l, p, t at line start). |
11423 | | /// Uses memchr for SIMD-accelerated newline finding. Handles both \n and |
11424 | | /// bare \r (old Mac) line endings. |
11425 | 0 | static bool opt_buffer_requires_legacy_fallback(const char *buf, size_t len) { |
11426 | 0 | if (!buf || len == 0) return false; |
11427 | | // Check at start of buffer |
11428 | 0 | if (opt_line_start_is_legacy(buf, len)) return true; |
11429 | | // Scan for line breaks (\n first, then bare \r) |
11430 | 0 | for (int pass = 0; pass < 2; pass++) { |
11431 | 0 | const char needle = (pass == 0) ? '\n' : '\r'; |
11432 | 0 | const char *p = buf; |
11433 | 0 | size_t remaining = len; |
11434 | 0 | for (;;) { |
11435 | 0 | const char *nl = static_cast<const char *>( |
11436 | 0 | std::memchr(p, needle, remaining)); |
11437 | 0 | if (!nl) break; |
11438 | 0 | size_t offset = static_cast<size_t>(nl - buf) + 1; |
11439 | 0 | if (offset >= len) break; |
11440 | | // Skip \n in \r\n pair on the \r pass to avoid double-checking |
11441 | 0 | if (needle == '\r' && offset < len && buf[offset] == '\n') { |
11442 | 0 | p = buf + offset + 1; |
11443 | 0 | remaining = len - offset - 1; |
11444 | 0 | continue; |
11445 | 0 | } |
11446 | 0 | if (opt_line_start_is_legacy(buf + offset, len - offset)) return true; |
11447 | 0 | p = buf + offset; |
11448 | 0 | remaining = len - offset; |
11449 | 0 | } |
11450 | 0 | } |
11451 | 0 | return false; |
11452 | 0 | } |
11453 | | |
11454 | | /// |
11455 | | /// Trie-based string → float cache for repeated OBJ coordinate values. |
11456 | | /// Caches short float tokens (up to kMaxKeyLen chars) in a compact trie. |
11457 | | /// Alphabet: '0'-'9', '+', '-', '.', 'e', 'E' (15 chars). |
11458 | | /// Thread-local — each thread gets its own cache instance. |
11459 | | /// |
11460 | | class OptFloatCache { |
11461 | | public: |
11462 | | static const int kFp32MaxKeyLen = |
11463 | | std::numeric_limits<float>::digits10 + 2; // 8 |
11464 | | static const int kRealMaxKeyLen = |
11465 | | std::numeric_limits<real_t>::digits10 + 2; |
11466 | | static const int kAlphabetSize = 15; |
11467 | | |
11468 | | explicit OptFloatCache(int max_nodes = 1024, bool fp32_keys = true) |
11469 | 0 | : max_nodes_(max_nodes < 2 ? 2 : (max_nodes > 65535 ? 65535 : max_nodes)), |
11470 | 0 | max_key_len_(fp32_keys ? kFp32MaxKeyLen : kRealMaxKeyLen) { |
11471 | 0 | nodes_.reserve(static_cast<size_t>(max_nodes_)); |
11472 | 0 | nodes_.resize(1); |
11473 | 0 | std::memset(&nodes_[0], 0, sizeof(Node)); |
11474 | 0 | } |
11475 | | |
11476 | 0 | static inline int char_index(char c) { |
11477 | 0 | if (c >= '0' && c <= '9') return c - '0'; |
11478 | 0 | switch (c) { |
11479 | 0 | case '+': return 10; |
11480 | 0 | case '-': return 11; |
11481 | 0 | case '.': return 12; |
11482 | 0 | case 'e': return 13; |
11483 | 0 | case 'E': return 14; |
11484 | 0 | } |
11485 | 0 | return -1; |
11486 | 0 | } |
11487 | | |
11488 | | /// Try cache lookup. Returns true + sets *out on hit. |
11489 | 0 | inline bool lookup(const char *key, int key_len, real_t *out) const { |
11490 | 0 | if (key_len <= 0 || key_len > max_key_len_) return false; |
11491 | 0 | int node = 0; |
11492 | 0 | for (int i = 0; i < key_len; i++) { |
11493 | 0 | int ci = char_index(key[i]); |
11494 | 0 | if (ci < 0) return false; |
11495 | 0 | int child = nodes_[static_cast<size_t>(node)] |
11496 | 0 | .children[static_cast<size_t>(ci)]; |
11497 | 0 | if (child == 0) return false; |
11498 | 0 | node = child; |
11499 | 0 | } |
11500 | 0 | const Node &n = nodes_[static_cast<size_t>(node)]; |
11501 | 0 | if (!n.has_value) return false; |
11502 | 0 | *out = n.value; |
11503 | 0 | return true; |
11504 | 0 | } |
11505 | | |
11506 | | /// Insert parsed value. Returns false if full or key too long. |
11507 | 0 | inline bool insert(const char *key, int key_len, real_t value) { |
11508 | 0 | if (key_len <= 0 || key_len > max_key_len_) return false; |
11509 | 0 | int node = 0; |
11510 | 0 | for (int i = 0; i < key_len; i++) { |
11511 | 0 | int ci = char_index(key[i]); |
11512 | 0 | if (ci < 0) return false; |
11513 | 0 | uint16_t child = nodes_[static_cast<size_t>(node)] |
11514 | 0 | .children[static_cast<size_t>(ci)]; |
11515 | 0 | if (child == 0) { |
11516 | 0 | if (static_cast<int>(nodes_.size()) >= max_nodes_) return false; |
11517 | 0 | child = static_cast<uint16_t>(nodes_.size()); |
11518 | | // Write back BEFORE push_back (push_back may realloc) |
11519 | 0 | nodes_[static_cast<size_t>(node)] |
11520 | 0 | .children[static_cast<size_t>(ci)] = child; |
11521 | 0 | Node new_node; |
11522 | 0 | std::memset(&new_node, 0, sizeof(Node)); |
11523 | 0 | nodes_.push_back(new_node); |
11524 | 0 | } |
11525 | 0 | node = static_cast<int>(child); |
11526 | 0 | } |
11527 | 0 | Node &n = nodes_[static_cast<size_t>(node)]; |
11528 | 0 | n.value = value; |
11529 | 0 | n.has_value = true; |
11530 | 0 | return true; |
11531 | 0 | } |
11532 | | |
11533 | 0 | int max_key_len() const { return max_key_len_; } |
11534 | | |
11535 | | private: |
11536 | | struct Node { |
11537 | | uint16_t children[kAlphabetSize]; // 30 bytes |
11538 | | real_t value; // 4 or 8 bytes |
11539 | | bool has_value; // 1 byte |
11540 | | }; |
11541 | | std::vector<Node> nodes_; |
11542 | | int max_nodes_; |
11543 | | int max_key_len_; |
11544 | | }; |
11545 | | |
11546 | | // Fast inline float parse: skip whitespace, call fast_float with line_end, |
11547 | | // advance cursor. No token-end pre-scan. Returns false if no float found. |
11548 | | // When cache is non-null, checks/populates the trie cache for short tokens. |
11549 | | // Handles nan/inf keywords with OBJ-compatible replacement values (matching |
11550 | | // opt_tryParseDouble behavior). |
11551 | | #ifndef TINYOBJLOADER_DISABLE_FAST_FLOAT |
11552 | | static inline bool opt_fast_parse_float(real_t *out, const char **cursor, |
11553 | | const char *line_end, |
11554 | 0 | OptFloatCache *cache = nullptr) { |
11555 | 0 | const char *p = *cursor; |
11556 | 0 | while (p < line_end && (*p == ' ' || *p == '\t')) p++; |
11557 | 0 | if (p >= line_end || *p == '\n' || *p == '\r' || *p == '#') return false; |
11558 | | |
11559 | | // Check for nan/inf keywords before calling fast_float (which doesn't |
11560 | | // handle them). Map to OBJ-compatible values: nan→0, +inf→max, -inf→lowest. |
11561 | 0 | { |
11562 | 0 | const char *q = p; |
11563 | 0 | if (q < line_end && (*q == '+' || *q == '-')) ++q; |
11564 | 0 | if (q < line_end) { |
11565 | 0 | char fc = *q; |
11566 | 0 | if (fc >= 'A' && fc <= 'Z') fc += 32; |
11567 | 0 | if (fc == 'n' || fc == 'i') { |
11568 | 0 | double special_val; |
11569 | 0 | const char *end_ptr; |
11570 | 0 | if (detail_fp::tryParseNanInf(p, line_end, &special_val, &end_ptr)) { |
11571 | 0 | *out = static_cast<real_t>(special_val); |
11572 | 0 | *cursor = end_ptr; |
11573 | 0 | return true; |
11574 | 0 | } |
11575 | 0 | } |
11576 | 0 | } |
11577 | 0 | } |
11578 | | |
11579 | | // --- Cache fast path: scan up to max_key_len chars for trie lookup --- |
11580 | 0 | if (cache) { |
11581 | 0 | const char *tok_start = p; |
11582 | 0 | const char *kp = p; |
11583 | 0 | int klen = 0; |
11584 | 0 | int mkl = cache->max_key_len(); |
11585 | 0 | while (klen < mkl && kp < line_end) { |
11586 | 0 | char c = *kp; |
11587 | 0 | if (c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '#' || |
11588 | 0 | c == '\0') |
11589 | 0 | break; |
11590 | 0 | if (OptFloatCache::char_index(c) < 0) break; |
11591 | 0 | klen++; |
11592 | 0 | kp++; |
11593 | 0 | } |
11594 | | // Token is cacheable if it ended at a delimiter (not mid-token) |
11595 | 0 | bool at_delim = (kp >= line_end || *kp == ' ' || *kp == '\t' || |
11596 | 0 | *kp == '\n' || *kp == '\r' || *kp == '#' || *kp == '\0'); |
11597 | 0 | if (klen > 0 && at_delim) { |
11598 | 0 | real_t cached; |
11599 | 0 | if (cache->lookup(tok_start, klen, &cached)) { |
11600 | 0 | *out = cached; |
11601 | 0 | *cursor = kp; |
11602 | 0 | return true; |
11603 | 0 | } |
11604 | | // Cache miss — parse normally, then insert |
11605 | 0 | real_t tmp; |
11606 | 0 | auto r = fast_float::from_chars(tok_start, line_end, tmp, |
11607 | 0 | fast_float::chars_format::general | |
11608 | 0 | fast_float::chars_format::allow_leading_plus); |
11609 | 0 | if (r.ec != tinyobj_ff::ff_errc::ok) return false; |
11610 | 0 | *out = tmp; |
11611 | 0 | *cursor = r.ptr; |
11612 | 0 | cache->insert(tok_start, klen, tmp); |
11613 | 0 | return true; |
11614 | 0 | } |
11615 | | // Token too long or has non-float chars — fall through to uncached parse |
11616 | 0 | } |
11617 | | |
11618 | | // --- Uncached parse --- |
11619 | 0 | real_t tmp; |
11620 | 0 | auto r = fast_float::from_chars(p, line_end, tmp, |
11621 | 0 | fast_float::chars_format::general | |
11622 | 0 | fast_float::chars_format::allow_leading_plus); |
11623 | 0 | if (r.ec != tinyobj_ff::ff_errc::ok) return false; |
11624 | 0 | *out = tmp; |
11625 | 0 | *cursor = r.ptr; |
11626 | 0 | return true; |
11627 | 0 | } |
11628 | | #endif |
11629 | | |
11630 | | // Parse a single line and store into compact per-type thread data. |
11631 | | // Fast path: v/vn/vt lines are parsed directly using fast_float with line_end, |
11632 | | // bypassing OptCommand entirely. Face/meta lines fall back to opt_parseLine. |
11633 | | static void opt_parseLineToThreadData( |
11634 | | OptThreadData &td, const char *line_ptr, size_t line_len, |
11635 | | bool triangulate, size_t line_number, |
11636 | 0 | OptFloatCache *cache = nullptr) { |
11637 | 0 | const char *token = line_ptr; |
11638 | 0 | while (*token == ' ' || *token == '\t') token++; |
11639 | 0 | if (*token == '\n' || *token == '\r' || *token == '#' || *token == '\0') |
11640 | 0 | return; |
11641 | | |
11642 | | #ifdef TINYOBJLOADER_DISABLE_FAST_FLOAT |
11643 | | (void)cache; // only consumed by the fast_float fast path below |
11644 | | #else |
11645 | 0 | const char *line_end = line_ptr + line_len; |
11646 | | |
11647 | | // ---- Fast path: vertex position (v x y z [w] [r g b]) ---- |
11648 | 0 | if (token[0] == 'v' && (token[1] == ' ' || token[1] == '\t')) { |
11649 | 0 | const char *cur = token + 2; |
11650 | 0 | real_t x, y, z; |
11651 | 0 | if (!opt_fast_parse_float(&x, &cur, line_end, cache) || |
11652 | 0 | !opt_fast_parse_float(&y, &cur, line_end, cache) || |
11653 | 0 | !opt_fast_parse_float(&z, &cur, line_end, cache)) { |
11654 | 0 | td.error_line = line_number; |
11655 | 0 | td.error_message = "failed to parse `v' line"; |
11656 | 0 | return; |
11657 | 0 | } |
11658 | | // Write xyz (batch: resize + direct write) |
11659 | 0 | size_t off = td.v_pos.size(); |
11660 | 0 | td.v_pos.resize(off + 3); |
11661 | 0 | td.v_pos[off] = x; td.v_pos[off+1] = y; td.v_pos[off+2] = z; |
11662 | | |
11663 | | // Check for extra components (weight / color) |
11664 | 0 | real_t extra[4]; |
11665 | 0 | int nextra = 0; |
11666 | 0 | while (nextra < 4 && opt_fast_parse_float(&extra[nextra], &cur, line_end, cache)) |
11667 | 0 | nextra++; |
11668 | |
|
11669 | 0 | if (nextra == 1) { |
11670 | | // v x y z w (4 total) — weight only, no color |
11671 | 0 | if (!td.saw_any_weight) { |
11672 | 0 | td.saw_any_weight = true; |
11673 | 0 | td.v_weight.resize(td.num_v, real_t(1.0)); |
11674 | 0 | } |
11675 | 0 | td.v_weight.push_back(extra[0]); |
11676 | 0 | td.saw_missing_color = true; |
11677 | 0 | if (td.saw_any_color) { |
11678 | 0 | size_t coff = td.v_color.size(); |
11679 | 0 | td.v_color.resize(coff + 3); |
11680 | 0 | td.v_color[coff] = real_t(1.0); |
11681 | 0 | td.v_color[coff+1] = real_t(1.0); |
11682 | 0 | td.v_color[coff+2] = real_t(1.0); |
11683 | 0 | } |
11684 | 0 | } else if (nextra == 3) { |
11685 | | // v x y z r g b (6 total) — color, weight = r (legacy compat) |
11686 | 0 | if (!td.saw_any_weight) { |
11687 | 0 | td.saw_any_weight = true; |
11688 | 0 | td.v_weight.resize(td.num_v, real_t(1.0)); |
11689 | 0 | } |
11690 | 0 | td.v_weight.push_back(extra[0]); |
11691 | 0 | if (!td.saw_any_color) { |
11692 | 0 | td.saw_any_color = true; |
11693 | 0 | td.v_color.resize(td.num_v * 3, real_t(1.0)); |
11694 | 0 | } |
11695 | 0 | size_t coff = td.v_color.size(); |
11696 | 0 | td.v_color.resize(coff + 3); |
11697 | 0 | td.v_color[coff] = extra[0]; |
11698 | 0 | td.v_color[coff+1] = extra[1]; |
11699 | 0 | td.v_color[coff+2] = extra[2]; |
11700 | 0 | } else if (nextra >= 4) { |
11701 | | // v x y z w r g b (7+ total) — weight + color. |
11702 | | // NOTE: 7-component vertices are NOT part of the OBJ spec nor the common |
11703 | | // vertex-color extension, which define only xyz / xyzw / xyzrgb and treat |
11704 | | // weight and color as mutually exclusive. tinyobjloader accepts this as |
11705 | | // an extension: the 4th value is the weight, the next three are RGB. |
11706 | | // (The classic LoadObj / LoadObjWithCallback path instead caps at 6 and |
11707 | | // ignores any extra tokens, so it differs from LoadObjOpt here.) |
11708 | 0 | if (!td.saw_any_weight) { |
11709 | 0 | td.saw_any_weight = true; |
11710 | 0 | td.v_weight.resize(td.num_v, real_t(1.0)); |
11711 | 0 | } |
11712 | 0 | td.v_weight.push_back(extra[0]); |
11713 | 0 | if (!td.saw_any_color) { |
11714 | 0 | td.saw_any_color = true; |
11715 | 0 | td.v_color.resize(td.num_v * 3, real_t(1.0)); |
11716 | 0 | } |
11717 | 0 | size_t coff = td.v_color.size(); |
11718 | 0 | td.v_color.resize(coff + 3); |
11719 | 0 | td.v_color[coff] = extra[1]; |
11720 | 0 | td.v_color[coff+1] = extra[2]; |
11721 | 0 | td.v_color[coff+2] = extra[3]; |
11722 | 0 | } else { |
11723 | | // nextra == 0 or 2 — no color |
11724 | 0 | if (td.saw_any_weight) td.v_weight.push_back(real_t(1.0)); |
11725 | 0 | td.saw_missing_color = true; |
11726 | 0 | if (td.saw_any_color) { |
11727 | 0 | size_t coff = td.v_color.size(); |
11728 | 0 | td.v_color.resize(coff + 3); |
11729 | 0 | td.v_color[coff] = real_t(1.0); |
11730 | 0 | td.v_color[coff+1] = real_t(1.0); |
11731 | 0 | td.v_color[coff+2] = real_t(1.0); |
11732 | 0 | } |
11733 | 0 | } |
11734 | 0 | td.num_v++; |
11735 | 0 | return; |
11736 | 0 | } |
11737 | | |
11738 | | // ---- Fast path: normal (vn x y z) ---- |
11739 | 0 | if (token[0] == 'v' && token[1] == 'n' && |
11740 | 0 | (token[2] == ' ' || token[2] == '\t')) { |
11741 | 0 | const char *cur = token + 3; |
11742 | 0 | real_t x, y, z; |
11743 | 0 | if (!opt_fast_parse_float(&x, &cur, line_end, cache) || |
11744 | 0 | !opt_fast_parse_float(&y, &cur, line_end, cache) || |
11745 | 0 | !opt_fast_parse_float(&z, &cur, line_end, cache)) { |
11746 | 0 | td.error_line = line_number; |
11747 | 0 | td.error_message = "failed to parse `vn' line"; |
11748 | 0 | return; |
11749 | 0 | } |
11750 | 0 | size_t off = td.vn_data.size(); |
11751 | 0 | td.vn_data.resize(off + 3); |
11752 | 0 | td.vn_data[off] = x; td.vn_data[off+1] = y; td.vn_data[off+2] = z; |
11753 | 0 | td.num_vn++; |
11754 | 0 | return; |
11755 | 0 | } |
11756 | | |
11757 | | // ---- Fast path: texcoord (vt u [v [w]]) ---- |
11758 | 0 | if (token[0] == 'v' && token[1] == 't' && |
11759 | 0 | (token[2] == ' ' || token[2] == '\t')) { |
11760 | 0 | const char *cur = token + 3; |
11761 | 0 | real_t u = 0, v = 0; |
11762 | 0 | if (!opt_fast_parse_float(&u, &cur, line_end, cache)) { |
11763 | 0 | td.error_line = line_number; |
11764 | 0 | td.error_message = "failed to parse `vt' line"; |
11765 | 0 | return; |
11766 | 0 | } |
11767 | 0 | opt_fast_parse_float(&v, &cur, line_end, cache); // v is optional |
11768 | 0 | real_t w = 0; |
11769 | 0 | bool has_w = opt_fast_parse_float(&w, &cur, line_end, cache); |
11770 | |
|
11771 | 0 | size_t off = td.vt_data.size(); |
11772 | 0 | td.vt_data.resize(off + 2); |
11773 | 0 | td.vt_data[off] = u; td.vt_data[off+1] = v; |
11774 | 0 | if (has_w) { |
11775 | 0 | if (!td.saw_any_texcoord_w) { |
11776 | 0 | td.saw_any_texcoord_w = true; |
11777 | 0 | td.vt_w.resize(td.num_vt, real_t(0.0)); |
11778 | 0 | } |
11779 | 0 | td.vt_w.push_back(w); |
11780 | 0 | } else if (td.saw_any_texcoord_w) { |
11781 | 0 | td.vt_w.push_back(real_t(0.0)); |
11782 | 0 | } |
11783 | 0 | td.num_vt++; |
11784 | 0 | return; |
11785 | 0 | } |
11786 | 0 | #endif // TINYOBJLOADER_DISABLE_FAST_FLOAT |
11787 | | |
11788 | | // ---- Slow path: face / meta commands via opt_parseLine ---- |
11789 | 0 | OptCommand cmd; |
11790 | 0 | bool parse_error = false; |
11791 | 0 | bool ok = opt_parseLine(&cmd, line_ptr, line_len, triangulate, |
11792 | 0 | &parse_error, nullptr); |
11793 | 0 | if (parse_error) { |
11794 | 0 | td.error_line = line_number; |
11795 | 0 | std::string msg; |
11796 | 0 | opt_parseLine(&cmd, line_ptr, line_len, triangulate, nullptr, &msg); |
11797 | 0 | td.error_message = msg; |
11798 | 0 | return; |
11799 | 0 | } |
11800 | 0 | if (!ok) return; |
11801 | | |
11802 | 0 | switch (cmd.type) { |
11803 | | #ifdef TINYOBJLOADER_DISABLE_FAST_FLOAT |
11804 | | // When fast_float is disabled, v/vn/vt fall through to here |
11805 | | case OPT_CMD_V: { |
11806 | | size_t off = td.v_pos.size(); |
11807 | | td.v_pos.resize(off + 3); |
11808 | | td.v_pos[off] = cmd.vx; td.v_pos[off+1] = cmd.vy; td.v_pos[off+2] = cmd.vz; |
11809 | | if (cmd.has_vertex_weight) { |
11810 | | if (!td.saw_any_weight) { |
11811 | | td.saw_any_weight = true; |
11812 | | td.v_weight.resize(td.num_v, real_t(1.0)); |
11813 | | } |
11814 | | td.v_weight.push_back(cmd.vw); |
11815 | | } else if (td.saw_any_weight) { |
11816 | | td.v_weight.push_back(real_t(1.0)); |
11817 | | } |
11818 | | if (cmd.has_vertex_color) { |
11819 | | if (!td.saw_any_color) { |
11820 | | td.saw_any_color = true; |
11821 | | td.v_color.resize(td.num_v * 3, real_t(1.0)); |
11822 | | } |
11823 | | td.v_color.push_back(cmd.vc_r); |
11824 | | td.v_color.push_back(cmd.vc_g); |
11825 | | td.v_color.push_back(cmd.vc_b); |
11826 | | } else { |
11827 | | td.saw_missing_color = true; |
11828 | | if (td.saw_any_color) { |
11829 | | td.v_color.push_back(real_t(1.0)); |
11830 | | td.v_color.push_back(real_t(1.0)); |
11831 | | td.v_color.push_back(real_t(1.0)); |
11832 | | } |
11833 | | } |
11834 | | td.num_v++; |
11835 | | break; |
11836 | | } |
11837 | | case OPT_CMD_VN: { |
11838 | | size_t off = td.vn_data.size(); |
11839 | | td.vn_data.resize(off + 3); |
11840 | | td.vn_data[off] = cmd.nx; td.vn_data[off+1] = cmd.ny; td.vn_data[off+2] = cmd.nz; |
11841 | | td.num_vn++; |
11842 | | break; |
11843 | | } |
11844 | | case OPT_CMD_VT: { |
11845 | | size_t off = td.vt_data.size(); |
11846 | | td.vt_data.resize(off + 2); |
11847 | | td.vt_data[off] = cmd.tx; td.vt_data[off+1] = cmd.ty; |
11848 | | if (cmd.has_texcoord_w) { |
11849 | | if (!td.saw_any_texcoord_w) { |
11850 | | td.saw_any_texcoord_w = true; |
11851 | | td.vt_w.resize(td.num_vt, real_t(0.0)); |
11852 | | } |
11853 | | td.vt_w.push_back(cmd.tw); |
11854 | | } else if (td.saw_any_texcoord_w) { |
11855 | | td.vt_w.push_back(real_t(0.0)); |
11856 | | } |
11857 | | td.num_vt++; |
11858 | | break; |
11859 | | } |
11860 | | #endif // TINYOBJLOADER_DISABLE_FAST_FLOAT |
11861 | 0 | case OPT_CMD_F: { |
11862 | 0 | OptFaceCmd fc; |
11863 | 0 | fc.face_vertex_count = cmd.face_vertex_count; |
11864 | 0 | fc.emitted_face_count = cmd.emitted_face_count; |
11865 | 0 | fc.emitted_face_verts = cmd.emitted_face_verts; |
11866 | 0 | fc.f_count = cmd.f_count; |
11867 | 0 | fc.degenerate = cmd.degenerate_face; |
11868 | 0 | fc.source_line = static_cast<uint32_t>(line_number); |
11869 | 0 | fc.v_count_before = static_cast<uint32_t>(td.num_v); |
11870 | 0 | fc.vn_count_before = static_cast<uint32_t>(td.num_vn); |
11871 | 0 | fc.vt_count_before = static_cast<uint32_t>(td.num_vt); |
11872 | 0 | if (!cmd.f_heap.empty()) { |
11873 | 0 | fc.f_heap = std::move(cmd.f_heap); |
11874 | 0 | } else if (cmd.face_vertex_count <= OptFaceCmd::kInlineCap) { |
11875 | 0 | for (uint32_t k = 0; k < cmd.face_vertex_count; k++) |
11876 | 0 | fc.f_inline[k] = cmd.f_inline[k]; |
11877 | 0 | } else { |
11878 | 0 | fc.f_heap.assign(cmd.f_inline, cmd.f_inline + cmd.face_vertex_count); |
11879 | 0 | } |
11880 | 0 | td.num_f_indices += fc.f_count; |
11881 | 0 | td.num_f_faces += fc.emitted_face_count; |
11882 | 0 | OptSeqEntry se; |
11883 | 0 | se.kind = OptSeqEntry::SEQ_FACE; |
11884 | 0 | se.index = static_cast<uint32_t>(td.faces.size()); |
11885 | 0 | td.seq.push_back(se); |
11886 | 0 | td.faces.push_back(std::move(fc)); |
11887 | 0 | break; |
11888 | 0 | } |
11889 | 0 | case OPT_CMD_G: |
11890 | 0 | case OPT_CMD_O: |
11891 | 0 | case OPT_CMD_USEMTL: |
11892 | 0 | case OPT_CMD_MTLLIB: |
11893 | 0 | case OPT_CMD_S: { |
11894 | 0 | OptMetaCmd mc; |
11895 | 0 | mc.type = cmd.type; |
11896 | 0 | mc.source_line = static_cast<uint32_t>(line_number); |
11897 | 0 | if (cmd.type == OPT_CMD_G) { |
11898 | 0 | mc.str_storage = cmd.group_name_storage; |
11899 | 0 | mc.str_ptr = cmd.group_name; |
11900 | 0 | mc.str_len = cmd.group_name_len; |
11901 | 0 | mc.group_name_empty = cmd.group_name_empty; |
11902 | 0 | } else if (cmd.type == OPT_CMD_O) { |
11903 | 0 | mc.str_ptr = cmd.object_name; |
11904 | 0 | mc.str_len = cmd.object_name_len; |
11905 | 0 | } else if (cmd.type == OPT_CMD_USEMTL) { |
11906 | 0 | mc.str_ptr = cmd.material_name; |
11907 | 0 | mc.str_len = cmd.material_name_len; |
11908 | 0 | } else if (cmd.type == OPT_CMD_MTLLIB) { |
11909 | 0 | mc.str_ptr = cmd.mtllib_name; |
11910 | 0 | mc.str_len = cmd.mtllib_name_len; |
11911 | 0 | } else if (cmd.type == OPT_CMD_S) { |
11912 | 0 | mc.smoothing_group_id = cmd.smoothing_group_id; |
11913 | 0 | td.saw_any_smoothing = true; |
11914 | 0 | } |
11915 | 0 | OptSeqEntry se; |
11916 | 0 | se.kind = OptSeqEntry::SEQ_META; |
11917 | 0 | se.index = static_cast<uint32_t>(td.metas.size()); |
11918 | 0 | td.seq.push_back(se); |
11919 | 0 | td.metas.push_back(std::move(mc)); |
11920 | 0 | break; |
11921 | 0 | } |
11922 | 0 | default: |
11923 | 0 | break; |
11924 | 0 | } |
11925 | 0 | } |
11926 | | |
11927 | | } // namespace opt_internal |
11928 | | |
11929 | | // Internal implementation with optional basedir for material path resolution |
11930 | | static bool LoadObjOpt_internal(basic_attrib_t<> *attrib, |
11931 | | std::vector<basic_shape_t<>> *shapes, |
11932 | | std::vector<material_t> *materials, |
11933 | | std::string *warn, std::string *err, |
11934 | | const char *buf, size_t buf_len, |
11935 | | const std::string &mtl_basedir, |
11936 | | const std::string &source_name, |
11937 | | bool enable_mtllib_loading, |
11938 | 0 | const OptLoadConfig &config) { |
11939 | 0 | using namespace opt_internal; |
11940 | |
|
11941 | 0 | if (!attrib || !shapes) { |
11942 | 0 | if (err) *err = "attrib and shapes must not be null."; |
11943 | 0 | return false; |
11944 | 0 | } |
11945 | | |
11946 | 0 | attrib->vertices.clear(); |
11947 | 0 | attrib->vertex_weights.clear(); |
11948 | 0 | attrib->normals.clear(); |
11949 | 0 | attrib->texcoords.clear(); |
11950 | 0 | attrib->texcoord_ws.clear(); |
11951 | 0 | attrib->colors.clear(); |
11952 | 0 | attrib->skin_weights.clear(); |
11953 | 0 | attrib->indices.clear(); |
11954 | 0 | attrib->face_num_verts.clear(); |
11955 | 0 | attrib->material_ids.clear(); |
11956 | 0 | shapes->clear(); |
11957 | 0 | if (materials) materials->clear(); |
11958 | |
|
11959 | 0 | if (buf_len < 1) return true; // empty buffer is not an error |
11960 | | |
11961 | 0 | if (!buf) { |
11962 | 0 | if (err) *err = "buf must not be null when buf_len > 0."; |
11963 | 0 | return false; |
11964 | 0 | } |
11965 | | |
11966 | | // Ensure buffer ends with a newline for safe tokenization (avoids |
11967 | | // one-byte over-read on the last line when parsing directly from the |
11968 | | // buffer without per-line copies). |
11969 | 0 | const char *work_buf = buf; |
11970 | 0 | size_t work_len = buf_len; |
11971 | 0 | std::vector<char> buf_with_sentinel; |
11972 | 0 | if (buf[buf_len - 1] != '\n') { |
11973 | 0 | buf_with_sentinel.assign(buf, buf + buf_len); |
11974 | 0 | buf_with_sentinel.push_back('\n'); |
11975 | 0 | work_buf = buf_with_sentinel.data(); |
11976 | 0 | work_len = buf_with_sentinel.size(); |
11977 | 0 | } |
11978 | | |
11979 | | // Determine thread count |
11980 | 0 | int num_threads = 1; |
11981 | | #ifdef TINYOBJLOADER_USE_MULTITHREADING |
11982 | | if (config.num_threads < 0) { |
11983 | | num_threads = static_cast<int>(std::thread::hardware_concurrency()); |
11984 | | if (num_threads < 1) num_threads = 1; |
11985 | | } else if (config.num_threads > 1) { |
11986 | | num_threads = config.num_threads; |
11987 | | } |
11988 | | if (num_threads > kOptMaxThreads) num_threads = kOptMaxThreads; |
11989 | | #else |
11990 | 0 | #endif |
11991 | | |
11992 | | // ---- Phase 1: find line boundaries ---- |
11993 | 0 | std::vector<LineInfo> all_line_infos; |
11994 | |
|
11995 | | #if defined(TINYOBJLOADER_USE_SIMD) && \ |
11996 | | (defined(TINYOBJLOADER_SIMD_SSE2) || defined(TINYOBJLOADER_SIMD_AVX2) || \ |
11997 | | defined(TINYOBJLOADER_SIMD_NEON)) |
11998 | | { |
11999 | | std::vector<size_t> nl_positions; |
12000 | | nl_positions.reserve(work_len / 64); |
12001 | | simd_find_newlines(work_buf, work_len, nl_positions); |
12002 | | simd_build_line_infos(work_buf, work_len, nl_positions, all_line_infos); |
12003 | | } |
12004 | | #else |
12005 | 0 | { |
12006 | 0 | all_line_infos.reserve(work_len / 64); |
12007 | 0 | scalar_find_line_infos(work_buf, 0, work_len, all_line_infos); |
12008 | 0 | } |
12009 | 0 | #endif |
12010 | |
|
12011 | 0 | const size_t total_lines = all_line_infos.size(); |
12012 | 0 | if (total_lines == 0) return true; |
12013 | | |
12014 | | // Fast buffer-level check for legacy-only tokens (replaces per-line scan) |
12015 | 0 | if (opt_buffer_requires_legacy_fallback(work_buf, work_len)) { |
12016 | 0 | std::string input(buf, buf + buf_len); |
12017 | 0 | std::istringstream iss(input); |
12018 | 0 | attrib_t legacy_attrib; |
12019 | 0 | std::vector<shape_t> legacy_shapes; |
12020 | 0 | std::vector<material_t> legacy_materials; |
12021 | 0 | MaterialFileReader mat_reader(mtl_basedir); |
12022 | 0 | MaterialReader *reader = |
12023 | 0 | enable_mtllib_loading ? static_cast<MaterialReader *>(&mat_reader) : NULL; |
12024 | 0 | const bool ok = LoadObj(&legacy_attrib, &legacy_shapes, &legacy_materials, |
12025 | 0 | warn, err, &iss, reader, config.triangulate, false); |
12026 | 0 | if (!ok) { |
12027 | 0 | return false; |
12028 | 0 | } |
12029 | 0 | ConvertLegacyResultToBasic(legacy_attrib, legacy_shapes, legacy_materials, |
12030 | 0 | attrib, shapes, materials); |
12031 | 0 | return true; |
12032 | 0 | } |
12033 | | |
12034 | | // ---- Phase 2: parse lines (compact storage) ---- |
12035 | | // Single-threaded or multi-threaded depending on compile option. |
12036 | | |
12037 | 0 | const size_t num_t = static_cast<size_t>(num_threads); |
12038 | 0 | std::vector<OptThreadData> thread_data(num_t); |
12039 | |
|
12040 | | #ifdef TINYOBJLOADER_USE_MULTITHREADING |
12041 | | // Multi-threaded path |
12042 | | { |
12043 | | size_t lines_per_thread = total_lines / num_t; |
12044 | | std::vector<std::thread> workers; |
12045 | | workers.reserve(num_t); |
12046 | | |
12047 | | for (int t = 0; t < num_threads; t++) { |
12048 | | size_t start = static_cast<size_t>(t) * lines_per_thread; |
12049 | | size_t end = (t == num_threads - 1) |
12050 | | ? total_lines |
12051 | | : (static_cast<size_t>(t) + 1) * lines_per_thread; |
12052 | | |
12053 | | workers.emplace_back([&, t, start, end]() { |
12054 | | OptThreadData &td = thread_data[static_cast<size_t>(t)]; |
12055 | | size_t est_lines = end - start; |
12056 | | td.v_pos.reserve(est_lines * 2); |
12057 | | td.faces.reserve(est_lines / 3); |
12058 | | td.seq.reserve(est_lines / 3); |
12059 | | OptFloatCache *tc = nullptr; |
12060 | | #ifndef TINYOBJLOADER_DISABLE_FAST_FLOAT |
12061 | | OptFloatCache thread_cache(config.float_cache_max_nodes, |
12062 | | config.fp32_cache); |
12063 | | if (config.float_cache) tc = &thread_cache; |
12064 | | #endif |
12065 | | for (size_t i = start; i < end; i++) { |
12066 | | opt_parseLineToThreadData(td, &work_buf[all_line_infos[i].pos], |
12067 | | all_line_infos[i].len, config.triangulate, |
12068 | | i + 1, tc); |
12069 | | if (td.error_line != 0) break; |
12070 | | } |
12071 | | }); |
12072 | | } |
12073 | | for (auto &w : workers) w.join(); |
12074 | | } |
12075 | | |
12076 | | #else |
12077 | | // Single-threaded path |
12078 | 0 | { |
12079 | 0 | OptThreadData &td = thread_data[0]; |
12080 | 0 | td.v_pos.reserve(total_lines * 2); |
12081 | 0 | td.faces.reserve(total_lines / 3); |
12082 | 0 | td.seq.reserve(total_lines / 3); |
12083 | 0 | OptFloatCache *tc = nullptr; |
12084 | 0 | #ifndef TINYOBJLOADER_DISABLE_FAST_FLOAT |
12085 | 0 | OptFloatCache thread_cache(config.float_cache_max_nodes, |
12086 | 0 | config.fp32_cache); |
12087 | 0 | if (config.float_cache) tc = &thread_cache; |
12088 | 0 | #endif |
12089 | 0 | for (size_t i = 0; i < total_lines; i++) { |
12090 | 0 | opt_parseLineToThreadData(td, &work_buf[all_line_infos[i].pos], |
12091 | 0 | all_line_infos[i].len, config.triangulate, |
12092 | 0 | i + 1, tc); |
12093 | 0 | if (td.error_line != 0) break; |
12094 | 0 | } |
12095 | 0 | } |
12096 | 0 | #endif |
12097 | |
|
12098 | 0 | size_t first_error_line = 0; |
12099 | 0 | std::string first_error_message; |
12100 | 0 | for (size_t t = 0; t < num_t; t++) { |
12101 | 0 | if (thread_data[t].error_line == 0) continue; |
12102 | 0 | if (first_error_line == 0 || thread_data[t].error_line < first_error_line) { |
12103 | 0 | first_error_line = thread_data[t].error_line; |
12104 | 0 | first_error_message = thread_data[t].error_message; |
12105 | 0 | } |
12106 | 0 | } |
12107 | | |
12108 | | // ---- Phase 3: process sequential material state ---- |
12109 | | // Compute v/vn/vt prefix sums across threads |
12110 | 0 | std::vector<size_t> v_prefix(num_t), vn_prefix(num_t), vt_prefix(num_t); |
12111 | 0 | v_prefix[0] = vn_prefix[0] = vt_prefix[0] = 0; |
12112 | 0 | for (size_t t = 1; t < num_t; t++) { |
12113 | 0 | v_prefix[t] = v_prefix[t - 1] + thread_data[t - 1].num_v; |
12114 | 0 | vn_prefix[t] = vn_prefix[t - 1] + thread_data[t - 1].num_vn; |
12115 | 0 | vt_prefix[t] = vt_prefix[t - 1] + thread_data[t - 1].num_vt; |
12116 | 0 | } |
12117 | |
|
12118 | 0 | std::map<std::string, int> material_map; |
12119 | 0 | std::vector<int> initial_material_id(num_t, -1); |
12120 | 0 | size_t eof_pending_degenerate_faces = 0; |
12121 | 0 | size_t phase3_error_line = 0; |
12122 | 0 | std::string phase3_error_message; |
12123 | 0 | { |
12124 | 0 | MaterialFileReader mat_file_reader(mtl_basedir); |
12125 | 0 | std::set<std::string> material_filenames; |
12126 | 0 | std::vector<material_t> temp_materials; |
12127 | 0 | std::vector<material_t> *material_dst = materials ? materials : &temp_materials; |
12128 | 0 | int running_material_id = -1; |
12129 | 0 | size_t pending_degenerate_faces = 0; |
12130 | 0 | for (size_t t = 0; t < num_t; t++) { |
12131 | 0 | if (phase3_error_line != 0) { |
12132 | 0 | break; |
12133 | 0 | } |
12134 | | |
12135 | 0 | initial_material_id[t] = running_material_id; |
12136 | 0 | const OptThreadData &td = thread_data[t]; |
12137 | |
|
12138 | 0 | for (size_t si = 0; si < td.seq.size(); si++) { |
12139 | 0 | const OptSeqEntry &entry = td.seq[si]; |
12140 | |
|
12141 | 0 | if (entry.kind == OptSeqEntry::SEQ_FACE) { |
12142 | 0 | const OptFaceCmd &face = td.faces[entry.index]; |
12143 | | |
12144 | | // Compute running counts from prefix sums + per-face stored counts |
12145 | 0 | int rv = static_cast<int>(v_prefix[t] + face.v_count_before); |
12146 | 0 | int rvn = static_cast<int>(vn_prefix[t] + face.vn_count_before); |
12147 | 0 | int rvt = static_cast<int>(vt_prefix[t] + face.vt_count_before); |
12148 | |
|
12149 | 0 | if (first_error_line != 0 && face.source_line >= first_error_line) { |
12150 | 0 | continue; |
12151 | 0 | } |
12152 | | |
12153 | 0 | if (face.degenerate) { |
12154 | 0 | pending_degenerate_faces++; |
12155 | 0 | continue; |
12156 | 0 | } |
12157 | | |
12158 | 0 | for (size_t k = 0; k < face.face_vertex_count; k++) { |
12159 | 0 | const opt_index_t &raw = face.face_indices()[k]; |
12160 | 0 | int resolved_idx = -1; |
12161 | |
|
12162 | 0 | if (!opt_validateAndResolveFaceIndexLikeLegacy( |
12163 | 0 | raw.vertex_index, rv, false, source_name, |
12164 | 0 | face.source_line, warn, &resolved_idx)) { |
12165 | 0 | phase3_error_line = face.source_line; |
12166 | 0 | phase3_error_message = |
12167 | 0 | "failed to parse `f' line (invalid vertex index)"; |
12168 | 0 | break; |
12169 | 0 | } |
12170 | | |
12171 | 0 | if (raw.texcoord_index != opt_index_t::kNotPresent) { |
12172 | 0 | if (!opt_validateAndResolveFaceIndexLikeLegacy( |
12173 | 0 | raw.texcoord_index, rvt, true, source_name, |
12174 | 0 | face.source_line, warn, &resolved_idx)) { |
12175 | 0 | phase3_error_line = face.source_line; |
12176 | 0 | phase3_error_message = |
12177 | 0 | "failed to parse `f' line (invalid vertex index)"; |
12178 | 0 | break; |
12179 | 0 | } |
12180 | 0 | } |
12181 | | |
12182 | 0 | if (raw.normal_index != opt_index_t::kNotPresent) { |
12183 | 0 | if (!opt_validateAndResolveFaceIndexLikeLegacy( |
12184 | 0 | raw.normal_index, rvn, true, source_name, |
12185 | 0 | face.source_line, warn, &resolved_idx)) { |
12186 | 0 | phase3_error_line = face.source_line; |
12187 | 0 | phase3_error_message = |
12188 | 0 | "failed to parse `f' line (invalid vertex index)"; |
12189 | 0 | break; |
12190 | 0 | } |
12191 | 0 | } |
12192 | 0 | } |
12193 | |
|
12194 | 0 | if (phase3_error_line != 0) { |
12195 | 0 | break; |
12196 | 0 | } |
12197 | | |
12198 | 0 | continue; |
12199 | 0 | } |
12200 | | |
12201 | | // SEQ_META |
12202 | 0 | OptMetaCmd &meta = thread_data[t].metas[entry.index]; |
12203 | |
|
12204 | 0 | if (first_error_line != 0 && meta.source_line >= first_error_line) { |
12205 | 0 | continue; |
12206 | 0 | } |
12207 | | |
12208 | 0 | if (meta.type == OPT_CMD_G || meta.type == OPT_CMD_O) { |
12209 | 0 | for (size_t deg = 0; deg < pending_degenerate_faces; deg++) { |
12210 | 0 | if (warn) { |
12211 | 0 | (*warn) += "Degenerated face found\n."; |
12212 | 0 | } |
12213 | 0 | } |
12214 | 0 | pending_degenerate_faces = 0; |
12215 | 0 | } |
12216 | |
|
12217 | 0 | if (meta.type == OPT_CMD_G && meta.group_name_empty) { |
12218 | 0 | if (warn) { |
12219 | 0 | std::stringstream ss; |
12220 | 0 | ss << "Empty group name. line: " << meta.source_line << "\n"; |
12221 | 0 | (*warn) += ss.str(); |
12222 | 0 | } |
12223 | 0 | continue; |
12224 | 0 | } |
12225 | | |
12226 | 0 | if (meta.type == OPT_CMD_MTLLIB) { |
12227 | 0 | if (!enable_mtllib_loading) { |
12228 | 0 | continue; |
12229 | 0 | } |
12230 | | |
12231 | 0 | std::string line_rest; |
12232 | 0 | if (meta.str_ptr && meta.str_len > 0) { |
12233 | 0 | line_rest.assign(meta.str_ptr, meta.str_len); |
12234 | 0 | } |
12235 | |
|
12236 | 0 | std::vector<std::string> filenames; |
12237 | 0 | SplitString(line_rest, ' ', '\\', filenames); |
12238 | 0 | RemoveEmptyTokens(&filenames); |
12239 | |
|
12240 | 0 | if (filenames.empty()) { |
12241 | 0 | if (warn) { |
12242 | 0 | std::stringstream ss; |
12243 | 0 | ss << "Looks like empty filename for mtllib. Use default " |
12244 | 0 | "material (line " |
12245 | 0 | << meta.source_line << ".)\n"; |
12246 | 0 | (*warn) += ss.str(); |
12247 | 0 | } |
12248 | 0 | continue; |
12249 | 0 | } |
12250 | | |
12251 | 0 | bool found = false; |
12252 | 0 | for (size_t s = 0; s < filenames.size(); s++) { |
12253 | 0 | if (material_filenames.count(filenames[s]) > 0) { |
12254 | 0 | found = true; |
12255 | 0 | continue; |
12256 | 0 | } |
12257 | | |
12258 | 0 | std::string warn_mtl; |
12259 | 0 | std::string err_mtl; |
12260 | 0 | bool ok = mat_file_reader(filenames[s], material_dst, &material_map, |
12261 | 0 | &warn_mtl, &err_mtl); |
12262 | |
|
12263 | 0 | if (warn && !warn_mtl.empty()) { |
12264 | 0 | (*warn) += warn_mtl; |
12265 | 0 | } |
12266 | |
|
12267 | 0 | if (err && !err_mtl.empty()) { |
12268 | 0 | (*err) += err_mtl; |
12269 | 0 | } |
12270 | |
|
12271 | 0 | if (ok) { |
12272 | 0 | found = true; |
12273 | 0 | material_filenames.insert(filenames[s]); |
12274 | 0 | break; |
12275 | 0 | } |
12276 | 0 | } |
12277 | |
|
12278 | 0 | if (!found) { |
12279 | 0 | if (warn) { |
12280 | 0 | (*warn) += |
12281 | 0 | "Failed to load material file(s). Use default material.\n"; |
12282 | 0 | } |
12283 | 0 | } |
12284 | 0 | continue; |
12285 | 0 | } |
12286 | | |
12287 | 0 | if (meta.type == OPT_CMD_USEMTL) { |
12288 | 0 | std::string mat_name; |
12289 | 0 | if (meta.str_ptr && meta.str_len > 0) { |
12290 | 0 | mat_name.assign(meta.str_ptr, meta.str_len); |
12291 | 0 | } |
12292 | 0 | while (!mat_name.empty() && |
12293 | 0 | (mat_name.back() == '\r' || mat_name.back() == '\n')) { |
12294 | 0 | mat_name.pop_back(); |
12295 | 0 | } |
12296 | |
|
12297 | 0 | std::map<std::string, int>::const_iterator it = material_map.find(mat_name); |
12298 | 0 | if (it != material_map.end()) { |
12299 | 0 | meta.resolved_material_id = it->second; |
12300 | 0 | } else { |
12301 | 0 | meta.resolved_material_id = -1; |
12302 | 0 | if (warn) { |
12303 | 0 | (*warn) += "material [ '" + mat_name + "' ] not found in .mtl\n"; |
12304 | 0 | } |
12305 | 0 | } |
12306 | 0 | running_material_id = meta.resolved_material_id; |
12307 | 0 | continue; |
12308 | 0 | } |
12309 | 0 | } |
12310 | |
|
12311 | 0 | } |
12312 | |
|
12313 | 0 | if (first_error_line == 0) { |
12314 | 0 | eof_pending_degenerate_faces = pending_degenerate_faces; |
12315 | 0 | } |
12316 | 0 | } |
12317 | |
|
12318 | 0 | if (phase3_error_line != 0) { |
12319 | 0 | if (err) { |
12320 | 0 | std::stringstream ss; |
12321 | 0 | ss << "Failed parse line(line " << phase3_error_line << "). " |
12322 | 0 | << phase3_error_message << "\n"; |
12323 | 0 | if (!err->empty()) { |
12324 | 0 | (*err) += ss.str(); |
12325 | 0 | } else { |
12326 | 0 | (*err) = ss.str(); |
12327 | 0 | } |
12328 | 0 | } |
12329 | 0 | return false; |
12330 | 0 | } |
12331 | | |
12332 | 0 | if (first_error_line != 0) { |
12333 | 0 | if (err) { |
12334 | 0 | std::stringstream ss; |
12335 | 0 | ss << "Failed parse line(line " << first_error_line << "). " |
12336 | 0 | << first_error_message << "\n"; |
12337 | 0 | if (!err->empty()) { |
12338 | 0 | (*err) += ss.str(); |
12339 | 0 | } else { |
12340 | 0 | (*err) = ss.str(); |
12341 | 0 | } |
12342 | 0 | } |
12343 | 0 | return false; |
12344 | 0 | } |
12345 | | |
12346 | | // ---- Phase 4: merge results ---- |
12347 | 0 | size_t num_v = 0, num_vn = 0, num_vt = 0; |
12348 | 0 | size_t total_idx = 0; // total index_t entries across all faces |
12349 | 0 | size_t total_faces = 0; // total emitted faces |
12350 | 0 | for (size_t t = 0; t < num_t; t++) { |
12351 | 0 | num_v += thread_data[t].num_v; |
12352 | 0 | num_vn += thread_data[t].num_vn; |
12353 | 0 | num_vt += thread_data[t].num_vt; |
12354 | 0 | total_idx += thread_data[t].num_f_indices; |
12355 | 0 | total_faces += thread_data[t].num_f_faces; |
12356 | 0 | } |
12357 | | |
12358 | | // Guard against size_t overflow in vertex/normal/texcoord allocation |
12359 | 0 | if (num_v > SIZE_MAX / 3 || num_vn > SIZE_MAX / 3 || num_vt > SIZE_MAX / 2) { |
12360 | 0 | if (err) { |
12361 | 0 | (*err) += "Integer overflow in vertex/normal/texcoord count.\n"; |
12362 | 0 | } |
12363 | 0 | return false; |
12364 | 0 | } |
12365 | | |
12366 | 0 | attrib->vertices.resize(num_v * 3); |
12367 | 0 | attrib->vertex_weights.resize(num_v, real_t(1.0)); |
12368 | 0 | attrib->normals.resize(num_vn * 3); |
12369 | 0 | attrib->texcoords.resize(num_vt * 2); |
12370 | 0 | attrib->texcoord_ws.resize(num_vt, real_t(0.0)); |
12371 | 0 | std::vector<unsigned int> all_smoothing_group_ids( |
12372 | 0 | static_cast<size_t>(total_faces), 0); |
12373 | 0 | attrib->indices.resize(total_idx); |
12374 | 0 | attrib->face_num_verts.resize(static_cast<size_t>(total_faces)); |
12375 | 0 | attrib->material_ids.resize(static_cast<size_t>(total_faces), -1); |
12376 | 0 | std::vector<unsigned char> thread_saw_any_vertex_color(num_t, 0); |
12377 | 0 | std::vector<unsigned char> thread_missing_vertex_color(num_t, 0); |
12378 | 0 | std::vector<real_t> all_colors(num_v * 3, real_t(1.0)); |
12379 | | |
12380 | | // Compute per-thread offsets |
12381 | 0 | std::vector<size_t> v_off(num_t), n_off(num_t), t_off(num_t), f_off(num_t), |
12382 | 0 | face_off(num_t); |
12383 | 0 | v_off[0] = n_off[0] = t_off[0] = f_off[0] = face_off[0] = 0; |
12384 | 0 | for (size_t t = 1; t < num_t; t++) { |
12385 | 0 | v_off[t] = v_off[t - 1] + thread_data[t - 1].num_v; |
12386 | 0 | n_off[t] = n_off[t - 1] + thread_data[t - 1].num_vn; |
12387 | 0 | t_off[t] = t_off[t - 1] + thread_data[t - 1].num_vt; |
12388 | 0 | f_off[t] = f_off[t - 1] + thread_data[t - 1].num_f_indices; |
12389 | 0 | face_off[t] = face_off[t - 1] + thread_data[t - 1].num_f_faces; |
12390 | 0 | } |
12391 | | |
12392 | | // Carry parser state that persists across lines, such as smoothing groups, |
12393 | | // across thread chunk boundaries before merging in parallel. |
12394 | 0 | std::vector<unsigned int> initial_smoothing_group_id(num_t, 0); |
12395 | 0 | unsigned int running_smoothing_group_id = 0; |
12396 | 0 | for (size_t t = 0; t < num_t; t++) { |
12397 | 0 | initial_smoothing_group_id[t] = running_smoothing_group_id; |
12398 | 0 | for (size_t mi = 0; mi < thread_data[t].metas.size(); mi++) { |
12399 | 0 | if (thread_data[t].metas[mi].type == OPT_CMD_S) { |
12400 | 0 | running_smoothing_group_id = thread_data[t].metas[mi].smoothing_group_id; |
12401 | 0 | } |
12402 | 0 | } |
12403 | 0 | } |
12404 | | |
12405 | | // Merge parsed data into final arrays |
12406 | 0 | std::vector<std::vector<unsigned int> > command_written_faces(num_t); |
12407 | 0 | for (size_t t = 0; t < num_t; t++) { |
12408 | 0 | command_written_faces[t].assign(thread_data[t].faces.size(), 0); |
12409 | 0 | } |
12410 | 0 | std::vector<size_t> written_index_counts(num_t, 0); |
12411 | 0 | std::vector<size_t> written_face_counts(num_t, 0); |
12412 | 0 | std::vector<size_t> merge_error_lines(num_t, 0); |
12413 | 0 | std::vector<std::string> merge_error_messages(num_t); |
12414 | 0 | std::vector<int> thread_greatest_v_idx(num_t, -1); |
12415 | 0 | std::vector<int> thread_greatest_vn_idx(num_t, -1); |
12416 | 0 | std::vector<int> thread_greatest_vt_idx(num_t, -1); |
12417 | 0 | auto merge_vertex_thread = [&](size_t t) { |
12418 | 0 | const OptThreadData &td = thread_data[t]; |
12419 | | // Bulk copy positions |
12420 | 0 | if (!td.v_pos.empty()) { |
12421 | 0 | std::memcpy(&attrib->vertices[v_off[t] * 3], td.v_pos.data(), |
12422 | 0 | td.v_pos.size() * sizeof(real_t)); |
12423 | 0 | } |
12424 | | // Copy weights |
12425 | 0 | if (td.saw_any_weight) { |
12426 | 0 | for (size_t i = 0; i < td.num_v; i++) |
12427 | 0 | attrib->vertex_weights[v_off[t] + i] = td.v_weight[i]; |
12428 | 0 | } |
12429 | | // Copy colors |
12430 | 0 | if (td.saw_any_color) { |
12431 | 0 | std::memcpy(&all_colors[v_off[t] * 3], td.v_color.data(), |
12432 | 0 | td.v_color.size() * sizeof(real_t)); |
12433 | 0 | thread_saw_any_vertex_color[t] = 1; |
12434 | 0 | if (td.saw_missing_color) thread_missing_vertex_color[t] = 1; |
12435 | 0 | } else if (td.saw_missing_color) { |
12436 | 0 | thread_missing_vertex_color[t] = 1; |
12437 | 0 | } |
12438 | | // Bulk copy normals |
12439 | 0 | if (!td.vn_data.empty()) { |
12440 | 0 | std::memcpy(&attrib->normals[n_off[t] * 3], td.vn_data.data(), |
12441 | 0 | td.vn_data.size() * sizeof(real_t)); |
12442 | 0 | } |
12443 | | // Bulk copy texcoords |
12444 | 0 | if (!td.vt_data.empty()) { |
12445 | 0 | std::memcpy(&attrib->texcoords[t_off[t] * 2], td.vt_data.data(), |
12446 | 0 | td.vt_data.size() * sizeof(real_t)); |
12447 | 0 | } |
12448 | 0 | if (td.saw_any_texcoord_w) { |
12449 | 0 | for (size_t i = 0; i < td.num_vt; i++) |
12450 | 0 | attrib->texcoord_ws[t_off[t] + i] = td.vt_w[i]; |
12451 | 0 | } |
12452 | 0 | }; |
12453 | 0 | auto merge_thread = [&](size_t t) { |
12454 | 0 | const OptThreadData &td = thread_data[t]; |
12455 | 0 | size_t fc = f_off[t], fcc = face_off[t]; |
12456 | 0 | int current_mat_id = initial_material_id[t]; |
12457 | 0 | unsigned int current_smoothing_id = initial_smoothing_group_id[t]; |
12458 | 0 | int greatest_v_idx = -1; |
12459 | 0 | int greatest_vn_idx = -1; |
12460 | 0 | int greatest_vt_idx = -1; |
12461 | |
|
12462 | 0 | for (size_t si = 0; si < td.seq.size(); si++) { |
12463 | 0 | const OptSeqEntry &entry = td.seq[si]; |
12464 | 0 | if (entry.kind == OptSeqEntry::SEQ_META) { |
12465 | 0 | const OptMetaCmd &meta = td.metas[entry.index]; |
12466 | 0 | if (meta.type == OPT_CMD_USEMTL) |
12467 | 0 | current_mat_id = meta.resolved_material_id; |
12468 | 0 | else if (meta.type == OPT_CMD_S) |
12469 | 0 | current_smoothing_id = meta.smoothing_group_id; |
12470 | 0 | continue; |
12471 | 0 | } |
12472 | | // SEQ_FACE |
12473 | 0 | const OptFaceCmd &face = td.faces[entry.index]; |
12474 | 0 | if (face.degenerate) { |
12475 | 0 | command_written_faces[t][entry.index] = 0; |
12476 | 0 | continue; |
12477 | 0 | } |
12478 | | // Compute running counts for index resolution |
12479 | 0 | size_t vc = v_off[t] + face.v_count_before; |
12480 | 0 | size_t nc = n_off[t] + face.vn_count_before; |
12481 | 0 | size_t tc = t_off[t] + face.vt_count_before; |
12482 | |
|
12483 | 0 | std::vector<index_t> resolved_face(face.face_vertex_count); |
12484 | 0 | for (size_t k = 0; k < face.face_vertex_count; k++) { |
12485 | 0 | const opt_index_t &vi = face.face_indices()[k]; |
12486 | 0 | index_t idx; |
12487 | 0 | if (!opt_resolveIndexLikeLegacy(vi.vertex_index, |
12488 | 0 | static_cast<int>(vc), |
12489 | 0 | &idx.vertex_index, false)) { |
12490 | 0 | merge_error_lines[t] = face.source_line; |
12491 | 0 | merge_error_messages[t] = |
12492 | 0 | "failed to parse `f' line (invalid vertex index)"; |
12493 | 0 | return; |
12494 | 0 | } |
12495 | 0 | opt_updateGreatestIndex(idx.vertex_index, &greatest_v_idx); |
12496 | 0 | if (vi.texcoord_index == opt_index_t::kNotPresent) { |
12497 | 0 | idx.texcoord_index = -1; |
12498 | 0 | } else { |
12499 | 0 | if (!opt_resolveIndexLikeLegacy(vi.texcoord_index, |
12500 | 0 | static_cast<int>(tc), |
12501 | 0 | &idx.texcoord_index, true)) { |
12502 | 0 | merge_error_lines[t] = face.source_line; |
12503 | 0 | merge_error_messages[t] = |
12504 | 0 | "failed to parse `f' line (invalid vertex index)"; |
12505 | 0 | return; |
12506 | 0 | } |
12507 | 0 | if (idx.texcoord_index >= 0) { |
12508 | 0 | opt_updateGreatestIndex(idx.texcoord_index, &greatest_vt_idx); |
12509 | 0 | } |
12510 | 0 | } |
12511 | 0 | if (vi.normal_index == opt_index_t::kNotPresent) { |
12512 | 0 | idx.normal_index = -1; |
12513 | 0 | } else { |
12514 | 0 | if (!opt_resolveIndexLikeLegacy(vi.normal_index, |
12515 | 0 | static_cast<int>(nc), |
12516 | 0 | &idx.normal_index, true)) { |
12517 | 0 | merge_error_lines[t] = face.source_line; |
12518 | 0 | merge_error_messages[t] = |
12519 | 0 | "failed to parse `f' line (invalid vertex index)"; |
12520 | 0 | return; |
12521 | 0 | } |
12522 | 0 | if (idx.normal_index >= 0) { |
12523 | 0 | opt_updateGreatestIndex(idx.normal_index, &greatest_vn_idx); |
12524 | 0 | } |
12525 | 0 | } |
12526 | 0 | resolved_face[k] = idx; |
12527 | 0 | } |
12528 | 0 | size_t written_index_count = 0; |
12529 | 0 | size_t written_face_count = 0; |
12530 | 0 | if (config.triangulate) { |
12531 | 0 | written_index_count = opt_triangulate_face( |
12532 | 0 | attrib->vertices, resolved_face.data(), resolved_face.size(), |
12533 | 0 | &attrib->indices[fc]); |
12534 | 0 | written_face_count = written_index_count / 3; |
12535 | 0 | } else { |
12536 | 0 | written_index_count = resolved_face.size(); |
12537 | 0 | written_face_count = 1; |
12538 | 0 | for (size_t k = 0; k < resolved_face.size(); k++) { |
12539 | 0 | attrib->indices[fc + k] = resolved_face[k]; |
12540 | 0 | } |
12541 | 0 | } |
12542 | 0 | for (size_t k = 0; k < written_face_count; k++) { |
12543 | 0 | attrib->face_num_verts[fcc + k] = config.triangulate |
12544 | 0 | ? 3 |
12545 | 0 | : static_cast<int>(face.emitted_face_verts); |
12546 | 0 | attrib->material_ids[fcc + k] = current_mat_id; |
12547 | 0 | all_smoothing_group_ids[fcc + k] = current_smoothing_id; |
12548 | 0 | } |
12549 | 0 | command_written_faces[t][entry.index] = |
12550 | 0 | static_cast<unsigned int>(written_face_count); |
12551 | 0 | fc += written_index_count; |
12552 | 0 | fcc += written_face_count; |
12553 | 0 | } |
12554 | 0 | written_index_counts[t] = fc - f_off[t]; |
12555 | 0 | written_face_counts[t] = fcc - face_off[t]; |
12556 | 0 | thread_greatest_v_idx[t] = greatest_v_idx; |
12557 | 0 | thread_greatest_vn_idx[t] = greatest_vn_idx; |
12558 | 0 | thread_greatest_vt_idx[t] = greatest_vt_idx; |
12559 | 0 | }; |
12560 | |
|
12561 | | #ifdef TINYOBJLOADER_USE_MULTITHREADING |
12562 | | if (num_threads > 1) { |
12563 | | std::vector<std::thread> workers; |
12564 | | workers.reserve(num_t); |
12565 | | for (size_t t = 0; t < num_t; t++) { |
12566 | | workers.emplace_back([&, t]() { merge_vertex_thread(t); }); |
12567 | | } |
12568 | | for (auto &w : workers) w.join(); |
12569 | | workers.clear(); |
12570 | | for (size_t t = 0; t < num_t; t++) { |
12571 | | workers.emplace_back([&, t]() { merge_thread(t); }); |
12572 | | } |
12573 | | for (auto &w : workers) w.join(); |
12574 | | } else { |
12575 | | for (size_t t = 0; t < num_t; t++) merge_vertex_thread(t); |
12576 | | for (size_t t = 0; t < num_t; t++) merge_thread(t); |
12577 | | } |
12578 | | #else |
12579 | 0 | for (size_t t = 0; t < num_t; t++) merge_vertex_thread(t); |
12580 | 0 | for (size_t t = 0; t < num_t; t++) merge_thread(t); |
12581 | 0 | #endif |
12582 | |
|
12583 | 0 | size_t first_merge_error_line = 0; |
12584 | 0 | std::string first_merge_error_message; |
12585 | 0 | for (size_t t = 0; t < merge_error_lines.size(); t++) { |
12586 | 0 | if (merge_error_lines[t] == 0) continue; |
12587 | 0 | if (first_merge_error_line == 0 || |
12588 | 0 | merge_error_lines[t] < first_merge_error_line) { |
12589 | 0 | first_merge_error_line = merge_error_lines[t]; |
12590 | 0 | first_merge_error_message = merge_error_messages[t]; |
12591 | 0 | } |
12592 | 0 | } |
12593 | |
|
12594 | 0 | if (first_merge_error_line != 0) { |
12595 | 0 | attrib->vertices.clear(); |
12596 | 0 | attrib->vertex_weights.clear(); |
12597 | 0 | attrib->normals.clear(); |
12598 | 0 | attrib->texcoords.clear(); |
12599 | 0 | attrib->texcoord_ws.clear(); |
12600 | 0 | attrib->colors.clear(); |
12601 | 0 | attrib->skin_weights.clear(); |
12602 | 0 | attrib->indices.clear(); |
12603 | 0 | attrib->face_num_verts.clear(); |
12604 | 0 | attrib->material_ids.clear(); |
12605 | 0 | shapes->clear(); |
12606 | 0 | if (materials) materials->clear(); |
12607 | |
|
12608 | 0 | if (err) { |
12609 | 0 | std::stringstream ss; |
12610 | 0 | ss << "Failed parse line(line " << first_merge_error_line << "). " |
12611 | 0 | << first_merge_error_message << "\n"; |
12612 | 0 | if (!err->empty()) { |
12613 | 0 | (*err) += ss.str(); |
12614 | 0 | } else { |
12615 | 0 | (*err) = ss.str(); |
12616 | 0 | } |
12617 | 0 | } |
12618 | 0 | return false; |
12619 | 0 | } |
12620 | | |
12621 | 0 | bool saw_any_vertex_color = false; |
12622 | 0 | bool saw_missing_vertex_color = false; |
12623 | 0 | for (size_t t = 0; t < num_t; t++) { |
12624 | 0 | saw_any_vertex_color = |
12625 | 0 | saw_any_vertex_color || (thread_saw_any_vertex_color[t] != 0); |
12626 | 0 | saw_missing_vertex_color = |
12627 | 0 | saw_missing_vertex_color || (thread_missing_vertex_color[t] != 0); |
12628 | 0 | } |
12629 | |
|
12630 | 0 | if (saw_any_vertex_color && !saw_missing_vertex_color) { |
12631 | 0 | attrib->colors.swap(all_colors); |
12632 | 0 | } else { |
12633 | 0 | attrib->colors.clear(); |
12634 | 0 | } |
12635 | |
|
12636 | 0 | size_t actual_num_indices = 0; |
12637 | 0 | size_t actual_num_faces = 0; |
12638 | 0 | int greatest_v_idx = -1; |
12639 | 0 | int greatest_vn_idx = -1; |
12640 | 0 | int greatest_vt_idx = -1; |
12641 | 0 | for (size_t t = 0; t < num_t; t++) { |
12642 | 0 | actual_num_indices += written_index_counts[t]; |
12643 | 0 | actual_num_faces += written_face_counts[t]; |
12644 | 0 | opt_updateGreatestIndex(thread_greatest_v_idx[t], &greatest_v_idx); |
12645 | 0 | opt_updateGreatestIndex(thread_greatest_vn_idx[t], &greatest_vn_idx); |
12646 | 0 | opt_updateGreatestIndex(thread_greatest_vt_idx[t], &greatest_vt_idx); |
12647 | 0 | } |
12648 | | |
12649 | | // Compact face arrays to remove gaps left by threads that wrote fewer |
12650 | | // indices/faces than pre-allocated (e.g. triangulation returning 0 for |
12651 | | // faces with out-of-bounds vertex indices). |
12652 | 0 | if (num_t > 1) { |
12653 | 0 | size_t dst_idx = 0; |
12654 | 0 | size_t dst_face = 0; |
12655 | 0 | for (size_t t = 0; t < num_t; t++) { |
12656 | 0 | size_t src_idx = f_off[t]; |
12657 | 0 | size_t src_face = face_off[t]; |
12658 | 0 | size_t idx_count = written_index_counts[t]; |
12659 | 0 | size_t fc_count = written_face_counts[t]; |
12660 | 0 | if (dst_idx != src_idx && idx_count > 0) { |
12661 | 0 | std::memmove(&attrib->indices[dst_idx], &attrib->indices[src_idx], |
12662 | 0 | idx_count * sizeof(index_t)); |
12663 | 0 | } |
12664 | 0 | if (dst_face != src_face && fc_count > 0) { |
12665 | 0 | std::memmove(&attrib->face_num_verts[dst_face], |
12666 | 0 | &attrib->face_num_verts[src_face], |
12667 | 0 | fc_count * sizeof(int)); |
12668 | 0 | std::memmove(&attrib->material_ids[dst_face], |
12669 | 0 | &attrib->material_ids[src_face], |
12670 | 0 | fc_count * sizeof(int)); |
12671 | 0 | std::memmove(&all_smoothing_group_ids[dst_face], |
12672 | 0 | &all_smoothing_group_ids[src_face], |
12673 | 0 | fc_count * sizeof(unsigned int)); |
12674 | 0 | } |
12675 | 0 | dst_idx += idx_count; |
12676 | 0 | dst_face += fc_count; |
12677 | 0 | } |
12678 | 0 | } |
12679 | |
|
12680 | 0 | attrib->indices.resize(actual_num_indices); |
12681 | 0 | attrib->face_num_verts.resize(actual_num_faces); |
12682 | 0 | attrib->material_ids.resize(actual_num_faces); |
12683 | 0 | all_smoothing_group_ids.resize(actual_num_faces); |
12684 | |
|
12685 | 0 | opt_appendOutOfBoundsWarnings( |
12686 | 0 | warn, greatest_v_idx, greatest_vn_idx, greatest_vt_idx, |
12687 | 0 | static_cast<int>(attrib->vertices.size() / 3), |
12688 | 0 | static_cast<int>(attrib->normals.size() / 3), |
12689 | 0 | static_cast<int>(attrib->texcoords.size() / 2), |
12690 | 0 | all_line_infos.size() + 1); |
12691 | 0 | for (size_t deg = 0; deg < eof_pending_degenerate_faces; deg++) { |
12692 | 0 | if (warn) { |
12693 | 0 | (*warn) += "Degenerated face found\n."; |
12694 | 0 | } |
12695 | 0 | } |
12696 | | |
12697 | | // ---- Phase 5: construct shapes ---- |
12698 | 0 | { |
12699 | | // Precompute prefix-sum of index offsets for O(1) slicing |
12700 | 0 | const size_t total_faces = attrib->face_num_verts.size(); |
12701 | 0 | std::vector<size_t> idx_prefix(total_faces + 1); |
12702 | 0 | idx_prefix[0] = 0; |
12703 | 0 | for (size_t fi = 0; fi < total_faces; fi++) { |
12704 | 0 | idx_prefix[fi + 1] = |
12705 | 0 | idx_prefix[fi] + static_cast<size_t>(attrib->face_num_verts[fi]); |
12706 | 0 | } |
12707 | |
|
12708 | 0 | size_t face_count = 0; |
12709 | 0 | basic_shape_t<> shape; |
12710 | 0 | size_t face_prev_offset = 0; |
12711 | 0 | bool shape_has_face_record = false; |
12712 | 0 | bool have_active_shape_name = false; |
12713 | |
|
12714 | 0 | for (size_t t = 0; t < num_t; t++) { |
12715 | 0 | const OptThreadData &td = thread_data[t]; |
12716 | 0 | for (size_t si = 0; si < td.seq.size(); si++) { |
12717 | 0 | const OptSeqEntry &entry = td.seq[si]; |
12718 | 0 | if (entry.kind == OptSeqEntry::SEQ_META) { |
12719 | 0 | const OptMetaCmd &meta = td.metas[entry.index]; |
12720 | 0 | if (meta.type == OPT_CMD_O || meta.type == OPT_CMD_G) { |
12721 | 0 | std::string name; |
12722 | 0 | if (meta.type == OPT_CMD_O && meta.str_ptr) { |
12723 | 0 | name.assign(meta.str_ptr, meta.str_len); |
12724 | 0 | } else if (!meta.str_storage.empty()) { |
12725 | 0 | name = meta.str_storage; |
12726 | 0 | } else if (meta.str_ptr) { |
12727 | 0 | name.assign(meta.str_ptr, meta.str_len); |
12728 | 0 | } |
12729 | 0 | while (!name.empty() && |
12730 | 0 | (name.back() == '\r' || name.back() == '\n')) |
12731 | 0 | name.pop_back(); |
12732 | |
|
12733 | 0 | if (face_count == 0) { |
12734 | 0 | shape.name = name; |
12735 | 0 | face_prev_offset = 0; |
12736 | 0 | have_active_shape_name = true; |
12737 | 0 | } else { |
12738 | 0 | if (!have_active_shape_name) { |
12739 | | // faces before first group/object |
12740 | 0 | basic_shape_t<> prev_shape; |
12741 | 0 | prev_shape.mesh.num_face_vertices.assign( |
12742 | 0 | attrib->face_num_verts.begin(), |
12743 | 0 | attrib->face_num_verts.begin() + |
12744 | 0 | static_cast<std::ptrdiff_t>(face_count)); |
12745 | 0 | prev_shape.mesh.indices.assign( |
12746 | 0 | attrib->indices.begin(), |
12747 | 0 | attrib->indices.begin() + |
12748 | 0 | static_cast<std::ptrdiff_t>(idx_prefix[face_count])); |
12749 | 0 | prev_shape.mesh.material_ids.assign( |
12750 | 0 | attrib->material_ids.begin(), |
12751 | 0 | attrib->material_ids.begin() + |
12752 | 0 | static_cast<std::ptrdiff_t>(face_count)); |
12753 | 0 | prev_shape.mesh.smoothing_group_ids.assign( |
12754 | 0 | all_smoothing_group_ids.begin(), |
12755 | 0 | all_smoothing_group_ids.begin() + |
12756 | 0 | static_cast<std::ptrdiff_t>(face_count)); |
12757 | 0 | shapes->push_back(std::move(prev_shape)); |
12758 | 0 | } else if (face_count > face_prev_offset) { |
12759 | | // push previous shape |
12760 | 0 | basic_shape_t<> prev_shape; |
12761 | 0 | prev_shape.name = shape.name; |
12762 | 0 | prev_shape.mesh.num_face_vertices.assign( |
12763 | 0 | attrib->face_num_verts.begin() + |
12764 | 0 | static_cast<std::ptrdiff_t>(face_prev_offset), |
12765 | 0 | attrib->face_num_verts.begin() + |
12766 | 0 | static_cast<std::ptrdiff_t>(face_count)); |
12767 | 0 | prev_shape.mesh.indices.assign( |
12768 | 0 | attrib->indices.begin() + |
12769 | 0 | static_cast<std::ptrdiff_t>(idx_prefix[face_prev_offset]), |
12770 | 0 | attrib->indices.begin() + |
12771 | 0 | static_cast<std::ptrdiff_t>(idx_prefix[face_count])); |
12772 | 0 | prev_shape.mesh.material_ids.assign( |
12773 | 0 | attrib->material_ids.begin() + |
12774 | 0 | static_cast<std::ptrdiff_t>(face_prev_offset), |
12775 | 0 | attrib->material_ids.begin() + |
12776 | 0 | static_cast<std::ptrdiff_t>(face_count)); |
12777 | 0 | prev_shape.mesh.smoothing_group_ids.assign( |
12778 | 0 | all_smoothing_group_ids.begin() + |
12779 | 0 | static_cast<std::ptrdiff_t>(face_prev_offset), |
12780 | 0 | all_smoothing_group_ids.begin() + |
12781 | 0 | static_cast<std::ptrdiff_t>(face_count)); |
12782 | 0 | shapes->push_back(std::move(prev_shape)); |
12783 | 0 | } |
12784 | 0 | shape.name = name; |
12785 | 0 | face_prev_offset = face_count; |
12786 | 0 | have_active_shape_name = true; |
12787 | 0 | } |
12788 | 0 | shape_has_face_record = false; |
12789 | 0 | } |
12790 | 0 | } else { |
12791 | | // SEQ_FACE |
12792 | 0 | shape_has_face_record = true; |
12793 | 0 | face_count += command_written_faces[t][entry.index]; |
12794 | 0 | } |
12795 | 0 | } |
12796 | 0 | } |
12797 | | |
12798 | | // Final shape |
12799 | 0 | if (face_count > face_prev_offset || shape_has_face_record) { |
12800 | 0 | basic_shape_t<> final_shape; |
12801 | 0 | final_shape.name = shape.name; |
12802 | 0 | final_shape.mesh.num_face_vertices.assign( |
12803 | 0 | attrib->face_num_verts.begin() + |
12804 | 0 | static_cast<std::ptrdiff_t>(face_prev_offset), |
12805 | 0 | attrib->face_num_verts.begin() + |
12806 | 0 | static_cast<std::ptrdiff_t>(face_count)); |
12807 | 0 | final_shape.mesh.indices.assign( |
12808 | 0 | attrib->indices.begin() + |
12809 | 0 | static_cast<std::ptrdiff_t>(idx_prefix[face_prev_offset]), |
12810 | 0 | attrib->indices.begin() + |
12811 | 0 | static_cast<std::ptrdiff_t>(idx_prefix[face_count])); |
12812 | 0 | final_shape.mesh.material_ids.assign( |
12813 | 0 | attrib->material_ids.begin() + |
12814 | 0 | static_cast<std::ptrdiff_t>(face_prev_offset), |
12815 | 0 | attrib->material_ids.begin() + |
12816 | 0 | static_cast<std::ptrdiff_t>(face_count)); |
12817 | 0 | final_shape.mesh.smoothing_group_ids.assign( |
12818 | 0 | all_smoothing_group_ids.begin() + |
12819 | 0 | static_cast<std::ptrdiff_t>(face_prev_offset), |
12820 | 0 | all_smoothing_group_ids.begin() + |
12821 | 0 | static_cast<std::ptrdiff_t>(face_count)); |
12822 | 0 | shapes->push_back(std::move(final_shape)); |
12823 | 0 | } |
12824 | 0 | } |
12825 | |
|
12826 | 0 | return true; |
12827 | 0 | } |
12828 | | |
12829 | | // ---- LoadObjOpt (buffer version, public API) ---- |
12830 | | |
12831 | | bool LoadObjOpt(basic_attrib_t<> *attrib, |
12832 | | std::vector<basic_shape_t<>> *shapes, |
12833 | | std::vector<material_t> *materials, |
12834 | | std::string *warn, std::string *err, |
12835 | | const char *buf, size_t buf_len, |
12836 | 0 | const OptLoadConfig &config) { |
12837 | 0 | return LoadObjOpt_internal(attrib, shapes, materials, warn, err, |
12838 | 0 | buf, buf_len, std::string(), "<stream>", false, |
12839 | 0 | config); |
12840 | 0 | } |
12841 | | |
12842 | | // ---- LoadObjOpt (file version) ---- |
12843 | | |
12844 | | bool LoadObjOpt(basic_attrib_t<> *attrib, |
12845 | | std::vector<basic_shape_t<>> *shapes, |
12846 | | std::vector<material_t> *materials, |
12847 | | std::string *warn, std::string *err, |
12848 | | const char *filename, |
12849 | | const char *mtl_basedir, |
12850 | 0 | const OptLoadConfig &config) { |
12851 | 0 | if (!filename) { |
12852 | 0 | if (err) *err = "filename is null."; |
12853 | 0 | return false; |
12854 | 0 | } |
12855 | | |
12856 | 0 | std::string filepath(filename); |
12857 | | |
12858 | | // Resolve material base directory |
12859 | 0 | std::string baseDir; |
12860 | 0 | if (mtl_basedir) { |
12861 | 0 | baseDir = mtl_basedir; |
12862 | 0 | } else { |
12863 | | // Extract directory from filename |
12864 | 0 | size_t pos = filepath.find_last_of("/\\"); |
12865 | 0 | if (pos != std::string::npos) { |
12866 | 0 | baseDir = filepath.substr(0, pos + 1); |
12867 | 0 | } |
12868 | 0 | } |
12869 | 0 | if (!baseDir.empty()) { |
12870 | 0 | #ifndef _WIN32 |
12871 | 0 | const char dirsep = '/'; |
12872 | | #else |
12873 | | const char dirsep = '\\'; |
12874 | | #endif |
12875 | 0 | if (baseDir[baseDir.length() - 1] != dirsep) baseDir += dirsep; |
12876 | 0 | } |
12877 | |
|
12878 | | #ifdef TINYOBJLOADER_USE_MMAP |
12879 | | { |
12880 | | MappedFile mf; |
12881 | | if (mf.open(filepath.c_str())) { |
12882 | | return LoadObjOpt_internal(attrib, shapes, materials, warn, err, |
12883 | | mf.data, mf.size, baseDir, filepath, true, |
12884 | | config); |
12885 | | } |
12886 | | } |
12887 | | #endif |
12888 | |
|
12889 | | #ifdef _WIN32 |
12890 | | std::ifstream ifs(LongPathW(UTF8ToWchar(filepath)).c_str(), |
12891 | | std::ios::binary | std::ios::ate); |
12892 | | #else |
12893 | 0 | std::ifstream ifs(filepath.c_str(), std::ios::binary | std::ios::ate); |
12894 | 0 | #endif |
12895 | 0 | if (!ifs.is_open()) { |
12896 | 0 | if (err) *err = "Cannot open file: " + filepath; |
12897 | 0 | return false; |
12898 | 0 | } |
12899 | | |
12900 | 0 | std::streamsize fsize = ifs.tellg(); |
12901 | 0 | ifs.seekg(0, std::ios::beg); |
12902 | |
|
12903 | 0 | if (fsize <= 0) { |
12904 | 0 | return LoadObjOpt_internal(attrib, shapes, materials, warn, err, |
12905 | 0 | "", static_cast<size_t>(0), baseDir, filepath, |
12906 | 0 | true, config); |
12907 | 0 | } |
12908 | | |
12909 | 0 | std::vector<char> buf(static_cast<size_t>(fsize)); |
12910 | 0 | if (!ifs.read(buf.data(), fsize)) { |
12911 | 0 | if (err) *err = "Failed to read file: " + filepath; |
12912 | 0 | return false; |
12913 | 0 | } |
12914 | 0 | ifs.close(); |
12915 | | |
12916 | | // Parse the in-memory file buffer with baseDir for mtllib resolution. |
12917 | 0 | return LoadObjOpt_internal(attrib, shapes, materials, warn, err, |
12918 | 0 | buf.data(), static_cast<size_t>(fsize), baseDir, |
12919 | 0 | filepath, true, config); |
12920 | 0 | } |
12921 | | |
12922 | | // ---- LoadObjOptTyped internals ---- |
12923 | | |
12924 | | static bool LoadObjOptTyped_internal(OptResult *result, |
12925 | | std::string *warn, std::string *err, |
12926 | | const char *buf, size_t buf_len, |
12927 | | const std::string &mtl_basedir, |
12928 | | const std::string &source_name, |
12929 | | bool enable_mtllib_loading, |
12930 | 0 | const OptLoadConfig &config) { |
12931 | 0 | using namespace opt_internal; |
12932 | |
|
12933 | 0 | ArenaAllocator &arena = result->arena; |
12934 | 0 | OptAttrib &attrib = result->attrib; |
12935 | 0 | result->valid = false; |
12936 | |
|
12937 | 0 | if (buf_len < 1) { |
12938 | 0 | result->valid = true; |
12939 | 0 | return true; |
12940 | 0 | } |
12941 | 0 | if (!buf) { |
12942 | 0 | if (err) *err = "buf must not be null when buf_len > 0."; |
12943 | 0 | return false; |
12944 | 0 | } |
12945 | | |
12946 | 0 | const char *work_buf = buf; |
12947 | 0 | size_t work_len = buf_len; |
12948 | 0 | std::vector<char> buf_with_sentinel; |
12949 | 0 | if (buf[buf_len - 1] != '\n') { |
12950 | 0 | buf_with_sentinel.assign(buf, buf + buf_len); |
12951 | 0 | buf_with_sentinel.push_back('\n'); |
12952 | 0 | work_buf = buf_with_sentinel.data(); |
12953 | 0 | work_len = buf_with_sentinel.size(); |
12954 | 0 | } |
12955 | |
|
12956 | 0 | int num_threads = 1; |
12957 | | #ifdef TINYOBJLOADER_USE_MULTITHREADING |
12958 | | if (config.num_threads < 0) { |
12959 | | num_threads = static_cast<int>(std::thread::hardware_concurrency()); |
12960 | | if (num_threads < 1) num_threads = 1; |
12961 | | } else if (config.num_threads > 1) { |
12962 | | num_threads = config.num_threads; |
12963 | | } |
12964 | | if (num_threads > kOptMaxThreads) num_threads = kOptMaxThreads; |
12965 | | #else |
12966 | 0 | #endif |
12967 | | |
12968 | | // ---- Phase 1: find line boundaries ---- |
12969 | 0 | std::vector<LineInfo> all_line_infos; |
12970 | |
|
12971 | | #if defined(TINYOBJLOADER_USE_SIMD) && \ |
12972 | | (defined(TINYOBJLOADER_SIMD_SSE2) || defined(TINYOBJLOADER_SIMD_AVX2) || \ |
12973 | | defined(TINYOBJLOADER_SIMD_NEON)) |
12974 | | { |
12975 | | std::vector<size_t> nl_positions; |
12976 | | nl_positions.reserve(work_len / 64); |
12977 | | simd_find_newlines(work_buf, work_len, nl_positions); |
12978 | | simd_build_line_infos(work_buf, work_len, nl_positions, all_line_infos); |
12979 | | } |
12980 | | #else |
12981 | 0 | { |
12982 | 0 | all_line_infos.reserve(work_len / 64); |
12983 | 0 | scalar_find_line_infos(work_buf, 0, work_len, all_line_infos); |
12984 | 0 | } |
12985 | 0 | #endif |
12986 | |
|
12987 | 0 | const size_t total_lines = all_line_infos.size(); |
12988 | 0 | if (total_lines == 0) { |
12989 | 0 | result->valid = true; |
12990 | 0 | return true; |
12991 | 0 | } |
12992 | | |
12993 | | // Fast buffer-level check for legacy-only tokens (replaces per-line scan) |
12994 | 0 | if (opt_buffer_requires_legacy_fallback(work_buf, work_len)) { |
12995 | 0 | basic_attrib_t<> tmp_attrib; |
12996 | 0 | std::vector<basic_shape_t<>> tmp_shapes; |
12997 | 0 | std::vector<material_t> tmp_materials; |
12998 | 0 | std::string input(buf, buf + buf_len); |
12999 | 0 | std::istringstream iss(input); |
13000 | 0 | attrib_t legacy_attrib; |
13001 | 0 | std::vector<shape_t> legacy_shapes; |
13002 | 0 | std::vector<material_t> legacy_materials; |
13003 | 0 | MaterialFileReader mat_reader(mtl_basedir); |
13004 | 0 | MaterialReader *reader = |
13005 | 0 | enable_mtllib_loading ? static_cast<MaterialReader *>(&mat_reader) : NULL; |
13006 | 0 | const bool ok = LoadObj(&legacy_attrib, &legacy_shapes, &legacy_materials, |
13007 | 0 | warn, err, &iss, reader, config.triangulate, false); |
13008 | 0 | if (!ok) return false; |
13009 | 0 | ConvertLegacyResultToBasic(legacy_attrib, legacy_shapes, legacy_materials, |
13010 | 0 | &tmp_attrib, &tmp_shapes, &tmp_materials); |
13011 | 0 | #define TINYOBJ_COPY_VEC_TO_ARENA_(dst, src) \ |
13012 | 0 | do { \ |
13013 | 0 | if (!(src).empty()) { \ |
13014 | 0 | (dst).allocate(arena, (src).size()); \ |
13015 | 0 | std::memcpy((dst).data(), (src).data(), \ |
13016 | 0 | (src).size() * sizeof((src)[0])); \ |
13017 | 0 | } \ |
13018 | 0 | } while (0) |
13019 | 0 | TINYOBJ_COPY_VEC_TO_ARENA_(attrib.vertices, tmp_attrib.vertices); |
13020 | 0 | TINYOBJ_COPY_VEC_TO_ARENA_(attrib.vertex_weights, tmp_attrib.vertex_weights); |
13021 | 0 | TINYOBJ_COPY_VEC_TO_ARENA_(attrib.normals, tmp_attrib.normals); |
13022 | 0 | TINYOBJ_COPY_VEC_TO_ARENA_(attrib.texcoords, tmp_attrib.texcoords); |
13023 | 0 | TINYOBJ_COPY_VEC_TO_ARENA_(attrib.texcoord_ws, tmp_attrib.texcoord_ws); |
13024 | 0 | TINYOBJ_COPY_VEC_TO_ARENA_(attrib.colors, tmp_attrib.colors); |
13025 | 0 | TINYOBJ_COPY_VEC_TO_ARENA_(attrib.indices, tmp_attrib.indices); |
13026 | 0 | TINYOBJ_COPY_VEC_TO_ARENA_(attrib.face_num_verts, tmp_attrib.face_num_verts); |
13027 | 0 | TINYOBJ_COPY_VEC_TO_ARENA_(attrib.material_ids, tmp_attrib.material_ids); |
13028 | 0 | #undef TINYOBJ_COPY_VEC_TO_ARENA_ |
13029 | | // Flatten per-shape smoothing_group_ids into attrib |
13030 | 0 | { |
13031 | 0 | size_t total_sg = 0; |
13032 | 0 | for (size_t si = 0; si < tmp_shapes.size(); si++) |
13033 | 0 | total_sg += tmp_shapes[si].mesh.smoothing_group_ids.size(); |
13034 | 0 | if (total_sg > 0) { |
13035 | 0 | attrib.smoothing_group_ids.allocate(arena, total_sg); |
13036 | 0 | size_t off = 0; |
13037 | 0 | for (size_t si = 0; si < tmp_shapes.size(); si++) { |
13038 | 0 | const auto &sg = tmp_shapes[si].mesh.smoothing_group_ids; |
13039 | 0 | if (!sg.empty()) { |
13040 | 0 | std::memcpy(&attrib.smoothing_group_ids[off], sg.data(), |
13041 | 0 | sg.size() * sizeof(sg[0])); |
13042 | 0 | off += sg.size(); |
13043 | 0 | } |
13044 | 0 | } |
13045 | 0 | } |
13046 | 0 | } |
13047 | 0 | result->shapes.clear(); |
13048 | 0 | size_t idx_off = 0, face_off = 0; |
13049 | 0 | for (size_t si = 0; si < tmp_shapes.size(); si++) { |
13050 | 0 | OptShapeRange sr; |
13051 | 0 | sr.name = tmp_shapes[si].name; |
13052 | 0 | sr.face_offset = face_off; |
13053 | 0 | sr.face_count = tmp_shapes[si].mesh.num_face_vertices.size(); |
13054 | 0 | sr.index_offset = idx_off; |
13055 | 0 | sr.index_count = tmp_shapes[si].mesh.indices.size(); |
13056 | 0 | face_off += sr.face_count; |
13057 | 0 | idx_off += sr.index_count; |
13058 | 0 | result->shapes.push_back(std::move(sr)); |
13059 | 0 | } |
13060 | 0 | result->materials = tmp_materials; |
13061 | 0 | result->valid = true; |
13062 | 0 | return true; |
13063 | 0 | } |
13064 | | |
13065 | | // ---- Phase 2: parse lines (compact storage) ---- |
13066 | 0 | const size_t num_t = static_cast<size_t>(num_threads); |
13067 | 0 | std::vector<OptThreadData> thread_data(num_t); |
13068 | |
|
13069 | | #ifdef TINYOBJLOADER_USE_MULTITHREADING |
13070 | | { |
13071 | | size_t lines_per_thread = total_lines / num_t; |
13072 | | std::vector<std::thread> workers; |
13073 | | workers.reserve(num_t); |
13074 | | |
13075 | | for (int t = 0; t < num_threads; t++) { |
13076 | | size_t start = static_cast<size_t>(t) * lines_per_thread; |
13077 | | size_t end = (t == num_threads - 1) |
13078 | | ? total_lines |
13079 | | : (static_cast<size_t>(t) + 1) * lines_per_thread; |
13080 | | |
13081 | | workers.emplace_back([&, t, start, end]() { |
13082 | | OptThreadData &td = thread_data[static_cast<size_t>(t)]; |
13083 | | size_t est_lines = end - start; |
13084 | | td.v_pos.reserve(est_lines * 2); |
13085 | | td.faces.reserve(est_lines / 3); |
13086 | | td.seq.reserve(est_lines / 3); |
13087 | | OptFloatCache *tc = nullptr; |
13088 | | #ifndef TINYOBJLOADER_DISABLE_FAST_FLOAT |
13089 | | OptFloatCache thread_cache(config.float_cache_max_nodes, |
13090 | | config.fp32_cache); |
13091 | | if (config.float_cache) tc = &thread_cache; |
13092 | | #endif |
13093 | | for (size_t i = start; i < end; i++) { |
13094 | | opt_parseLineToThreadData(td, &work_buf[all_line_infos[i].pos], |
13095 | | all_line_infos[i].len, config.triangulate, |
13096 | | i + 1, tc); |
13097 | | if (td.error_line != 0) break; |
13098 | | } |
13099 | | }); |
13100 | | } |
13101 | | for (auto &w : workers) w.join(); |
13102 | | } |
13103 | | #else |
13104 | 0 | { |
13105 | 0 | OptThreadData &td = thread_data[0]; |
13106 | 0 | td.v_pos.reserve(total_lines * 2); |
13107 | 0 | td.faces.reserve(total_lines / 3); |
13108 | 0 | td.seq.reserve(total_lines / 3); |
13109 | 0 | OptFloatCache *tc = nullptr; |
13110 | 0 | #ifndef TINYOBJLOADER_DISABLE_FAST_FLOAT |
13111 | 0 | OptFloatCache thread_cache(config.float_cache_max_nodes, |
13112 | 0 | config.fp32_cache); |
13113 | 0 | if (config.float_cache) tc = &thread_cache; |
13114 | 0 | #endif |
13115 | 0 | for (size_t i = 0; i < total_lines; i++) { |
13116 | 0 | opt_parseLineToThreadData(td, &work_buf[all_line_infos[i].pos], |
13117 | 0 | all_line_infos[i].len, config.triangulate, |
13118 | 0 | i + 1, tc); |
13119 | 0 | if (td.error_line != 0) break; |
13120 | 0 | } |
13121 | 0 | } |
13122 | 0 | #endif |
13123 | | |
13124 | | // Check for parse errors |
13125 | 0 | size_t first_error_line = 0; |
13126 | 0 | std::string first_error_message; |
13127 | 0 | for (size_t t = 0; t < num_t; t++) { |
13128 | 0 | if (thread_data[t].error_line == 0) continue; |
13129 | 0 | if (first_error_line == 0 || thread_data[t].error_line < first_error_line) { |
13130 | 0 | first_error_line = thread_data[t].error_line; |
13131 | 0 | first_error_message = thread_data[t].error_message; |
13132 | 0 | } |
13133 | 0 | } |
13134 | | |
13135 | | // ---- Phase 3: process sequential material state ---- |
13136 | | // Compute v/vn/vt prefix sums across threads |
13137 | 0 | std::vector<size_t> v_prefix(num_t), vn_prefix(num_t), vt_prefix(num_t); |
13138 | 0 | v_prefix[0] = vn_prefix[0] = vt_prefix[0] = 0; |
13139 | 0 | for (size_t t = 1; t < num_t; t++) { |
13140 | 0 | v_prefix[t] = v_prefix[t - 1] + thread_data[t - 1].num_v; |
13141 | 0 | vn_prefix[t] = vn_prefix[t - 1] + thread_data[t - 1].num_vn; |
13142 | 0 | vt_prefix[t] = vt_prefix[t - 1] + thread_data[t - 1].num_vt; |
13143 | 0 | } |
13144 | |
|
13145 | 0 | std::map<std::string, int> material_map; |
13146 | 0 | std::vector<int> initial_material_id(num_t, -1); |
13147 | 0 | size_t eof_pending_degenerate_faces = 0; |
13148 | 0 | size_t phase3_error_line = 0; |
13149 | 0 | std::string phase3_error_message; |
13150 | 0 | { |
13151 | 0 | MaterialFileReader mat_file_reader(mtl_basedir); |
13152 | 0 | std::set<std::string> material_filenames; |
13153 | 0 | std::vector<material_t> *material_dst = &result->materials; |
13154 | 0 | int running_material_id = -1; |
13155 | 0 | size_t pending_degenerate_faces = 0; |
13156 | |
|
13157 | 0 | for (size_t t = 0; t < num_t; t++) { |
13158 | 0 | if (phase3_error_line != 0) break; |
13159 | 0 | initial_material_id[t] = running_material_id; |
13160 | 0 | const OptThreadData &td = thread_data[t]; |
13161 | |
|
13162 | 0 | for (size_t si = 0; si < td.seq.size(); si++) { |
13163 | 0 | const OptSeqEntry &entry = td.seq[si]; |
13164 | |
|
13165 | 0 | if (entry.kind == OptSeqEntry::SEQ_FACE) { |
13166 | 0 | const OptFaceCmd &face = td.faces[entry.index]; |
13167 | 0 | int rv = static_cast<int>(v_prefix[t] + face.v_count_before); |
13168 | 0 | int rvn = static_cast<int>(vn_prefix[t] + face.vn_count_before); |
13169 | 0 | int rvt = static_cast<int>(vt_prefix[t] + face.vt_count_before); |
13170 | |
|
13171 | 0 | if (first_error_line != 0 && face.source_line >= first_error_line) continue; |
13172 | | |
13173 | 0 | if (face.degenerate) { |
13174 | 0 | pending_degenerate_faces++; |
13175 | 0 | continue; |
13176 | 0 | } |
13177 | | |
13178 | 0 | for (size_t k = 0; k < face.face_vertex_count; k++) { |
13179 | 0 | const opt_index_t &raw = face.face_indices()[k]; |
13180 | 0 | int resolved_idx = -1; |
13181 | 0 | if (!opt_validateAndResolveFaceIndexLikeLegacy( |
13182 | 0 | raw.vertex_index, rv, false, source_name, |
13183 | 0 | face.source_line, warn, &resolved_idx)) { |
13184 | 0 | phase3_error_line = face.source_line; |
13185 | 0 | phase3_error_message = "failed to parse `f' line (invalid vertex index)"; |
13186 | 0 | break; |
13187 | 0 | } |
13188 | 0 | if (raw.texcoord_index != opt_index_t::kNotPresent) { |
13189 | 0 | if (!opt_validateAndResolveFaceIndexLikeLegacy( |
13190 | 0 | raw.texcoord_index, rvt, true, source_name, |
13191 | 0 | face.source_line, warn, &resolved_idx)) { |
13192 | 0 | phase3_error_line = face.source_line; |
13193 | 0 | phase3_error_message = "failed to parse `f' line (invalid vertex index)"; |
13194 | 0 | break; |
13195 | 0 | } |
13196 | 0 | } |
13197 | 0 | if (raw.normal_index != opt_index_t::kNotPresent) { |
13198 | 0 | if (!opt_validateAndResolveFaceIndexLikeLegacy( |
13199 | 0 | raw.normal_index, rvn, true, source_name, |
13200 | 0 | face.source_line, warn, &resolved_idx)) { |
13201 | 0 | phase3_error_line = face.source_line; |
13202 | 0 | phase3_error_message = "failed to parse `f' line (invalid vertex index)"; |
13203 | 0 | break; |
13204 | 0 | } |
13205 | 0 | } |
13206 | 0 | } |
13207 | 0 | if (phase3_error_line != 0) break; |
13208 | 0 | continue; |
13209 | 0 | } |
13210 | | |
13211 | | // SEQ_META |
13212 | 0 | OptMetaCmd &meta = thread_data[t].metas[entry.index]; |
13213 | |
|
13214 | 0 | if (first_error_line != 0 && meta.source_line >= first_error_line) continue; |
13215 | | |
13216 | 0 | if (meta.type == OPT_CMD_G || meta.type == OPT_CMD_O) { |
13217 | 0 | for (size_t deg = 0; deg < pending_degenerate_faces; deg++) { |
13218 | 0 | if (warn) (*warn) += "Degenerated face found\n."; |
13219 | 0 | } |
13220 | 0 | pending_degenerate_faces = 0; |
13221 | 0 | } |
13222 | |
|
13223 | 0 | if (meta.type == OPT_CMD_G && meta.group_name_empty) { |
13224 | 0 | if (warn) { |
13225 | 0 | (*warn) += "Empty group name. line: " + |
13226 | 0 | std::to_string(meta.source_line) + "\n"; |
13227 | 0 | } |
13228 | 0 | continue; |
13229 | 0 | } |
13230 | | |
13231 | 0 | if (meta.type == OPT_CMD_MTLLIB) { |
13232 | 0 | if (!enable_mtllib_loading) continue; |
13233 | 0 | std::string line_rest; |
13234 | 0 | if (meta.str_ptr && meta.str_len > 0) { |
13235 | 0 | line_rest.assign(meta.str_ptr, meta.str_len); |
13236 | 0 | } |
13237 | 0 | std::vector<std::string> filenames; |
13238 | 0 | SplitString(line_rest, ' ', '\\', filenames); |
13239 | 0 | RemoveEmptyTokens(&filenames); |
13240 | 0 | if (filenames.empty()) { |
13241 | 0 | if (warn) { |
13242 | 0 | (*warn) += "Looks like empty filename for mtllib. Use default " |
13243 | 0 | "material (line " + |
13244 | 0 | std::to_string(meta.source_line) + ".)\n"; |
13245 | 0 | } |
13246 | 0 | continue; |
13247 | 0 | } |
13248 | 0 | bool found = false; |
13249 | 0 | for (size_t s = 0; s < filenames.size(); s++) { |
13250 | 0 | if (material_filenames.count(filenames[s]) > 0) { |
13251 | 0 | found = true; |
13252 | 0 | continue; |
13253 | 0 | } |
13254 | 0 | std::string warn_mtl, err_mtl; |
13255 | 0 | bool ok = mat_file_reader(filenames[s], material_dst, &material_map, |
13256 | 0 | &warn_mtl, &err_mtl); |
13257 | 0 | if (warn && !warn_mtl.empty()) (*warn) += warn_mtl; |
13258 | 0 | if (err && !err_mtl.empty()) (*err) += err_mtl; |
13259 | 0 | if (ok) { |
13260 | 0 | found = true; |
13261 | 0 | material_filenames.insert(filenames[s]); |
13262 | 0 | break; |
13263 | 0 | } |
13264 | 0 | } |
13265 | 0 | if (!found && warn) { |
13266 | 0 | (*warn) += "Failed to load material file(s). Use default material.\n"; |
13267 | 0 | } |
13268 | 0 | continue; |
13269 | 0 | } |
13270 | | |
13271 | 0 | if (meta.type == OPT_CMD_USEMTL) { |
13272 | 0 | std::string mat_name; |
13273 | 0 | if (meta.str_ptr && meta.str_len > 0) { |
13274 | 0 | mat_name.assign(meta.str_ptr, meta.str_len); |
13275 | 0 | } |
13276 | 0 | while (!mat_name.empty() && |
13277 | 0 | (mat_name.back() == '\r' || mat_name.back() == '\n')) { |
13278 | 0 | mat_name.pop_back(); |
13279 | 0 | } |
13280 | 0 | std::map<std::string, int>::const_iterator it = material_map.find(mat_name); |
13281 | 0 | if (it != material_map.end()) { |
13282 | 0 | meta.resolved_material_id = it->second; |
13283 | 0 | } else { |
13284 | 0 | meta.resolved_material_id = -1; |
13285 | 0 | if (warn) (*warn) += "material [ '" + mat_name + "' ] not found in .mtl\n"; |
13286 | 0 | } |
13287 | 0 | running_material_id = meta.resolved_material_id; |
13288 | 0 | continue; |
13289 | 0 | } |
13290 | 0 | } |
13291 | |
|
13292 | 0 | } |
13293 | 0 | if (first_error_line == 0) { |
13294 | 0 | eof_pending_degenerate_faces = pending_degenerate_faces; |
13295 | 0 | } |
13296 | 0 | } |
13297 | |
|
13298 | 0 | if (phase3_error_line != 0) { |
13299 | 0 | if (err) { |
13300 | 0 | (*err) += "Failed parse line(line " + |
13301 | 0 | std::to_string(phase3_error_line) + "). " + |
13302 | 0 | phase3_error_message + "\n"; |
13303 | 0 | } |
13304 | 0 | return false; |
13305 | 0 | } |
13306 | | |
13307 | 0 | if (first_error_line != 0) { |
13308 | 0 | if (err) { |
13309 | 0 | (*err) += "Failed parse line(line " + |
13310 | 0 | std::to_string(first_error_line) + "). " + |
13311 | 0 | first_error_message + "\n"; |
13312 | 0 | } |
13313 | 0 | return false; |
13314 | 0 | } |
13315 | | |
13316 | | // ---- Phase 4: allocate arena arrays and merge ---- |
13317 | 0 | size_t num_v = 0, num_vn = 0, num_vt = 0; |
13318 | 0 | size_t total_idx = 0; // total index_t entries across all faces |
13319 | 0 | size_t total_faces = 0; // total emitted faces |
13320 | 0 | for (size_t t = 0; t < num_t; t++) { |
13321 | 0 | num_v += thread_data[t].num_v; |
13322 | 0 | num_vn += thread_data[t].num_vn; |
13323 | 0 | num_vt += thread_data[t].num_vt; |
13324 | 0 | total_idx += thread_data[t].num_f_indices; |
13325 | 0 | total_faces += thread_data[t].num_f_faces; |
13326 | 0 | } |
13327 | | |
13328 | | // Guard against size_t overflow in vertex/normal/texcoord allocation |
13329 | 0 | if (num_v > SIZE_MAX / 3 || num_vn > SIZE_MAX / 3 || num_vt > SIZE_MAX / 2) { |
13330 | 0 | if (err) { |
13331 | 0 | (*err) += "Integer overflow in vertex/normal/texcoord count.\n"; |
13332 | 0 | } |
13333 | 0 | return false; |
13334 | 0 | } |
13335 | | |
13336 | | // Determine which optional arrays are needed |
13337 | 0 | bool any_color = false; |
13338 | 0 | bool any_weight = false, any_texcoord_w = false, any_smoothing = false; |
13339 | 0 | for (size_t t = 0; t < num_t; t++) { |
13340 | 0 | if (thread_data[t].saw_any_color) any_color = true; |
13341 | 0 | if (thread_data[t].saw_any_weight) any_weight = true; |
13342 | 0 | if (thread_data[t].saw_any_texcoord_w) any_texcoord_w = true; |
13343 | 0 | if (thread_data[t].saw_any_smoothing) any_smoothing = true; |
13344 | 0 | } |
13345 | | |
13346 | | // Allocate from arena |
13347 | 0 | attrib.vertices.allocate(arena, num_v * 3); |
13348 | 0 | if (any_weight) { |
13349 | 0 | attrib.vertex_weights.allocate(arena, num_v); |
13350 | 0 | for (size_t i = 0; i < num_v; i++) |
13351 | 0 | attrib.vertex_weights[i] = real_t(1.0); |
13352 | 0 | } |
13353 | 0 | attrib.normals.allocate(arena, num_vn * 3); |
13354 | 0 | attrib.texcoords.allocate(arena, num_vt * 2); |
13355 | 0 | if (any_texcoord_w) { |
13356 | 0 | attrib.texcoord_ws.allocate(arena, num_vt); |
13357 | 0 | } |
13358 | 0 | attrib.indices.allocate(arena, total_idx); |
13359 | 0 | attrib.face_num_verts.allocate(arena, total_faces); |
13360 | 0 | attrib.material_ids.allocate(arena, total_faces); |
13361 | 0 | for (size_t i = 0; i < total_faces; i++) |
13362 | 0 | attrib.material_ids[i] = -1; |
13363 | | |
13364 | | // Smoothing group ids — only if any smoothing commands seen |
13365 | 0 | TypedArray<unsigned int> all_smoothing_group_ids; |
13366 | 0 | if (any_smoothing) { |
13367 | 0 | all_smoothing_group_ids.allocate(arena, total_faces); |
13368 | 0 | } |
13369 | | |
13370 | | // Colors — allocate temp only if any vertex has color |
13371 | 0 | TypedArray<real_t> all_colors; |
13372 | 0 | if (any_color) { |
13373 | 0 | all_colors.allocate(arena, num_v * 3); |
13374 | 0 | for (size_t i = 0; i < num_v * 3; i++) |
13375 | 0 | all_colors[i] = real_t(1.0); |
13376 | 0 | } |
13377 | | |
13378 | | // Compute per-thread offsets |
13379 | 0 | std::vector<size_t> v_off(num_t), n_off(num_t), t_off(num_t), f_off(num_t), |
13380 | 0 | face_off(num_t); |
13381 | 0 | v_off[0] = n_off[0] = t_off[0] = f_off[0] = face_off[0] = 0; |
13382 | 0 | for (size_t t = 1; t < num_t; t++) { |
13383 | 0 | v_off[t] = v_off[t - 1] + thread_data[t - 1].num_v; |
13384 | 0 | n_off[t] = n_off[t - 1] + thread_data[t - 1].num_vn; |
13385 | 0 | t_off[t] = t_off[t - 1] + thread_data[t - 1].num_vt; |
13386 | 0 | f_off[t] = f_off[t - 1] + thread_data[t - 1].num_f_indices; |
13387 | 0 | face_off[t] = face_off[t - 1] + thread_data[t - 1].num_f_faces; |
13388 | 0 | } |
13389 | | |
13390 | | // Carry smoothing group state across thread boundaries |
13391 | 0 | std::vector<unsigned int> initial_smoothing_group_id(num_t, 0); |
13392 | 0 | unsigned int running_smoothing_group_id = 0; |
13393 | 0 | for (size_t t = 0; t < num_t; t++) { |
13394 | 0 | initial_smoothing_group_id[t] = running_smoothing_group_id; |
13395 | 0 | for (size_t mi = 0; mi < thread_data[t].metas.size(); mi++) { |
13396 | 0 | if (thread_data[t].metas[mi].type == OPT_CMD_S) { |
13397 | 0 | running_smoothing_group_id = thread_data[t].metas[mi].smoothing_group_id; |
13398 | 0 | } |
13399 | 0 | } |
13400 | 0 | } |
13401 | | |
13402 | | // Per-thread face tracking for shape construction |
13403 | 0 | std::vector<std::vector<unsigned int>> command_written_faces(num_t); |
13404 | 0 | for (size_t t = 0; t < num_t; t++) { |
13405 | 0 | command_written_faces[t].assign(thread_data[t].faces.size(), 0); |
13406 | 0 | } |
13407 | 0 | std::vector<size_t> written_index_counts(num_t, 0); |
13408 | 0 | std::vector<size_t> written_face_counts(num_t, 0); |
13409 | 0 | std::vector<size_t> merge_error_lines(num_t, 0); |
13410 | 0 | std::vector<std::string> merge_error_messages(num_t); |
13411 | 0 | std::vector<int> thread_greatest_v_idx(num_t, -1); |
13412 | 0 | std::vector<int> thread_greatest_vn_idx(num_t, -1); |
13413 | 0 | std::vector<int> thread_greatest_vt_idx(num_t, -1); |
13414 | 0 | std::vector<unsigned char> thread_saw_any_vertex_color(num_t, 0); |
13415 | 0 | std::vector<unsigned char> thread_missing_vertex_color(num_t, 0); |
13416 | |
|
13417 | 0 | auto merge_vertex_thread = [&](size_t t) { |
13418 | 0 | const OptThreadData &td = thread_data[t]; |
13419 | | // Bulk copy positions |
13420 | 0 | if (!td.v_pos.empty()) { |
13421 | 0 | std::memcpy(&attrib.vertices[v_off[t] * 3], td.v_pos.data(), |
13422 | 0 | td.v_pos.size() * sizeof(real_t)); |
13423 | 0 | } |
13424 | | // Copy weights |
13425 | 0 | if (any_weight && td.saw_any_weight) { |
13426 | 0 | for (size_t i = 0; i < td.num_v; i++) |
13427 | 0 | attrib.vertex_weights[v_off[t] + i] = td.v_weight[i]; |
13428 | 0 | } |
13429 | | // Copy colors |
13430 | 0 | if (any_color) { |
13431 | 0 | if (td.saw_any_color) { |
13432 | 0 | std::memcpy(&all_colors[v_off[t] * 3], td.v_color.data(), |
13433 | 0 | td.v_color.size() * sizeof(real_t)); |
13434 | 0 | thread_saw_any_vertex_color[t] = 1; |
13435 | 0 | if (td.saw_missing_color) thread_missing_vertex_color[t] = 1; |
13436 | 0 | } else if (td.saw_missing_color) { |
13437 | 0 | thread_missing_vertex_color[t] = 1; |
13438 | 0 | } |
13439 | 0 | } |
13440 | | // Bulk copy normals |
13441 | 0 | if (!td.vn_data.empty()) { |
13442 | 0 | std::memcpy(&attrib.normals[n_off[t] * 3], td.vn_data.data(), |
13443 | 0 | td.vn_data.size() * sizeof(real_t)); |
13444 | 0 | } |
13445 | | // Bulk copy texcoords |
13446 | 0 | if (!td.vt_data.empty()) { |
13447 | 0 | std::memcpy(&attrib.texcoords[t_off[t] * 2], td.vt_data.data(), |
13448 | 0 | td.vt_data.size() * sizeof(real_t)); |
13449 | 0 | } |
13450 | 0 | if (any_texcoord_w && td.saw_any_texcoord_w) { |
13451 | 0 | for (size_t i = 0; i < td.num_vt; i++) |
13452 | 0 | attrib.texcoord_ws[t_off[t] + i] = td.vt_w[i]; |
13453 | 0 | } |
13454 | 0 | }; |
13455 | |
|
13456 | 0 | auto merge_thread = [&](size_t t) { |
13457 | 0 | const OptThreadData &td = thread_data[t]; |
13458 | 0 | size_t fc = f_off[t], fcc = face_off[t]; |
13459 | 0 | int current_mat_id = initial_material_id[t]; |
13460 | 0 | unsigned int current_smoothing_id = initial_smoothing_group_id[t]; |
13461 | 0 | int greatest_v_idx = -1; |
13462 | 0 | int greatest_vn_idx = -1; |
13463 | 0 | int greatest_vt_idx = -1; |
13464 | | |
13465 | | // Reusable scratch buffer for resolved face indices |
13466 | 0 | index_t resolved_inline[8]; |
13467 | 0 | std::vector<index_t> resolved_heap; |
13468 | |
|
13469 | 0 | for (size_t si = 0; si < td.seq.size(); si++) { |
13470 | 0 | const OptSeqEntry &entry = td.seq[si]; |
13471 | 0 | if (entry.kind == OptSeqEntry::SEQ_META) { |
13472 | 0 | const OptMetaCmd &meta = td.metas[entry.index]; |
13473 | 0 | if (meta.type == OPT_CMD_USEMTL) |
13474 | 0 | current_mat_id = meta.resolved_material_id; |
13475 | 0 | else if (meta.type == OPT_CMD_S) |
13476 | 0 | current_smoothing_id = meta.smoothing_group_id; |
13477 | 0 | continue; |
13478 | 0 | } |
13479 | | // SEQ_FACE |
13480 | 0 | const OptFaceCmd &face = td.faces[entry.index]; |
13481 | 0 | if (face.degenerate) { |
13482 | 0 | command_written_faces[t][entry.index] = 0; |
13483 | 0 | continue; |
13484 | 0 | } |
13485 | | // Compute running counts for index resolution |
13486 | 0 | size_t vc = v_off[t] + face.v_count_before; |
13487 | 0 | size_t nc = n_off[t] + face.vn_count_before; |
13488 | 0 | size_t tc = t_off[t] + face.vt_count_before; |
13489 | | |
13490 | | // Use stack buffer for typical faces, heap only for large polygons |
13491 | 0 | index_t *resolved_face; |
13492 | 0 | if (face.face_vertex_count <= 8) { |
13493 | 0 | resolved_face = resolved_inline; |
13494 | 0 | } else { |
13495 | 0 | resolved_heap.resize(face.face_vertex_count); |
13496 | 0 | resolved_face = resolved_heap.data(); |
13497 | 0 | } |
13498 | 0 | for (size_t k = 0; k < face.face_vertex_count; k++) { |
13499 | 0 | const opt_index_t &vi = face.face_indices()[k]; |
13500 | 0 | index_t idx; |
13501 | 0 | if (!opt_resolveIndexLikeLegacy(vi.vertex_index, |
13502 | 0 | static_cast<int>(vc), |
13503 | 0 | &idx.vertex_index, false)) { |
13504 | 0 | merge_error_lines[t] = face.source_line; |
13505 | 0 | merge_error_messages[t] = |
13506 | 0 | "failed to parse `f' line (invalid vertex index)"; |
13507 | 0 | return; |
13508 | 0 | } |
13509 | 0 | opt_updateGreatestIndex(idx.vertex_index, &greatest_v_idx); |
13510 | 0 | if (vi.texcoord_index == opt_index_t::kNotPresent) { |
13511 | 0 | idx.texcoord_index = -1; |
13512 | 0 | } else { |
13513 | 0 | if (!opt_resolveIndexLikeLegacy(vi.texcoord_index, |
13514 | 0 | static_cast<int>(tc), |
13515 | 0 | &idx.texcoord_index, true)) { |
13516 | 0 | merge_error_lines[t] = face.source_line; |
13517 | 0 | merge_error_messages[t] = |
13518 | 0 | "failed to parse `f' line (invalid vertex index)"; |
13519 | 0 | return; |
13520 | 0 | } |
13521 | 0 | if (idx.texcoord_index >= 0) |
13522 | 0 | opt_updateGreatestIndex(idx.texcoord_index, &greatest_vt_idx); |
13523 | 0 | } |
13524 | 0 | if (vi.normal_index == opt_index_t::kNotPresent) { |
13525 | 0 | idx.normal_index = -1; |
13526 | 0 | } else { |
13527 | 0 | if (!opt_resolveIndexLikeLegacy(vi.normal_index, |
13528 | 0 | static_cast<int>(nc), |
13529 | 0 | &idx.normal_index, true)) { |
13530 | 0 | merge_error_lines[t] = face.source_line; |
13531 | 0 | merge_error_messages[t] = |
13532 | 0 | "failed to parse `f' line (invalid vertex index)"; |
13533 | 0 | return; |
13534 | 0 | } |
13535 | 0 | if (idx.normal_index >= 0) |
13536 | 0 | opt_updateGreatestIndex(idx.normal_index, &greatest_vn_idx); |
13537 | 0 | } |
13538 | 0 | resolved_face[k] = idx; |
13539 | 0 | } |
13540 | 0 | size_t written_index_count = 0; |
13541 | 0 | size_t written_face_count = 0; |
13542 | 0 | if (config.triangulate) { |
13543 | 0 | written_index_count = opt_triangulate_face( |
13544 | 0 | attrib.vertices.data(), attrib.vertices.size(), |
13545 | 0 | resolved_face, face.face_vertex_count, |
13546 | 0 | &attrib.indices[fc]); |
13547 | 0 | written_face_count = written_index_count / 3; |
13548 | 0 | } else { |
13549 | 0 | written_index_count = face.face_vertex_count; |
13550 | 0 | written_face_count = 1; |
13551 | 0 | for (size_t k = 0; k < face.face_vertex_count; k++) { |
13552 | 0 | attrib.indices[fc + k] = resolved_face[k]; |
13553 | 0 | } |
13554 | 0 | } |
13555 | 0 | for (size_t k = 0; k < written_face_count; k++) { |
13556 | 0 | attrib.face_num_verts[fcc + k] = config.triangulate |
13557 | 0 | ? 3 |
13558 | 0 | : static_cast<int>(face.emitted_face_verts); |
13559 | 0 | attrib.material_ids[fcc + k] = current_mat_id; |
13560 | 0 | if (any_smoothing) { |
13561 | 0 | all_smoothing_group_ids[fcc + k] = current_smoothing_id; |
13562 | 0 | } |
13563 | 0 | } |
13564 | 0 | command_written_faces[t][entry.index] = |
13565 | 0 | static_cast<unsigned int>(written_face_count); |
13566 | 0 | fc += written_index_count; |
13567 | 0 | fcc += written_face_count; |
13568 | 0 | } |
13569 | 0 | written_index_counts[t] = fc - f_off[t]; |
13570 | 0 | written_face_counts[t] = fcc - face_off[t]; |
13571 | 0 | thread_greatest_v_idx[t] = greatest_v_idx; |
13572 | 0 | thread_greatest_vn_idx[t] = greatest_vn_idx; |
13573 | 0 | thread_greatest_vt_idx[t] = greatest_vt_idx; |
13574 | 0 | }; |
13575 | |
|
13576 | | #ifdef TINYOBJLOADER_USE_MULTITHREADING |
13577 | | if (num_threads > 1) { |
13578 | | std::vector<std::thread> workers; |
13579 | | workers.reserve(num_t); |
13580 | | for (size_t t = 0; t < num_t; t++) { |
13581 | | workers.emplace_back([&, t]() { merge_vertex_thread(t); }); |
13582 | | } |
13583 | | for (auto &w : workers) w.join(); |
13584 | | workers.clear(); |
13585 | | for (size_t t = 0; t < num_t; t++) { |
13586 | | workers.emplace_back([&, t]() { merge_thread(t); }); |
13587 | | } |
13588 | | for (auto &w : workers) w.join(); |
13589 | | } else { |
13590 | | for (size_t t = 0; t < num_t; t++) merge_vertex_thread(t); |
13591 | | for (size_t t = 0; t < num_t; t++) merge_thread(t); |
13592 | | } |
13593 | | #else |
13594 | 0 | for (size_t t = 0; t < num_t; t++) merge_vertex_thread(t); |
13595 | 0 | for (size_t t = 0; t < num_t; t++) merge_thread(t); |
13596 | 0 | #endif |
13597 | | |
13598 | | // Check merge errors |
13599 | 0 | size_t first_merge_error_line = 0; |
13600 | 0 | std::string first_merge_error_message; |
13601 | 0 | for (size_t t = 0; t < merge_error_lines.size(); t++) { |
13602 | 0 | if (merge_error_lines[t] == 0) continue; |
13603 | 0 | if (first_merge_error_line == 0 || |
13604 | 0 | merge_error_lines[t] < first_merge_error_line) { |
13605 | 0 | first_merge_error_line = merge_error_lines[t]; |
13606 | 0 | first_merge_error_message = merge_error_messages[t]; |
13607 | 0 | } |
13608 | 0 | } |
13609 | |
|
13610 | 0 | if (first_merge_error_line != 0) { |
13611 | 0 | if (err) { |
13612 | 0 | (*err) += "Failed parse line(line " + |
13613 | 0 | std::to_string(first_merge_error_line) + "). " + |
13614 | 0 | first_merge_error_message + "\n"; |
13615 | 0 | } |
13616 | 0 | return false; |
13617 | 0 | } |
13618 | | |
13619 | | // Handle vertex colors: only keep if all vertices have color |
13620 | 0 | { |
13621 | 0 | bool saw_any = false, saw_missing = false; |
13622 | 0 | for (size_t t = 0; t < num_t; t++) { |
13623 | 0 | saw_any = saw_any || (thread_saw_any_vertex_color[t] != 0); |
13624 | 0 | saw_missing = saw_missing || (thread_missing_vertex_color[t] != 0); |
13625 | 0 | } |
13626 | 0 | if (saw_any && !saw_missing) { |
13627 | 0 | attrib.colors = all_colors; // share arena pointer |
13628 | 0 | } |
13629 | | // else attrib.colors stays empty (default) |
13630 | 0 | } |
13631 | | |
13632 | | // Compute actual sizes and compact/truncate |
13633 | 0 | size_t actual_num_indices = 0; |
13634 | 0 | size_t actual_num_faces = 0; |
13635 | 0 | int greatest_v_idx = -1, greatest_vn_idx = -1, greatest_vt_idx = -1; |
13636 | 0 | for (size_t t = 0; t < num_t; t++) { |
13637 | 0 | actual_num_indices += written_index_counts[t]; |
13638 | 0 | actual_num_faces += written_face_counts[t]; |
13639 | 0 | opt_updateGreatestIndex(thread_greatest_v_idx[t], &greatest_v_idx); |
13640 | 0 | opt_updateGreatestIndex(thread_greatest_vn_idx[t], &greatest_vn_idx); |
13641 | 0 | opt_updateGreatestIndex(thread_greatest_vt_idx[t], &greatest_vt_idx); |
13642 | 0 | } |
13643 | | |
13644 | | // Compact face arrays to remove gaps left by threads that wrote fewer |
13645 | | // indices/faces than pre-allocated (e.g. triangulation returning 0 for |
13646 | | // faces with out-of-bounds vertex indices). |
13647 | 0 | if (num_t > 1) { |
13648 | 0 | size_t dst_idx = 0; |
13649 | 0 | size_t dst_face = 0; |
13650 | 0 | for (size_t t = 0; t < num_t; t++) { |
13651 | 0 | size_t src_idx = f_off[t]; |
13652 | 0 | size_t src_face = face_off[t]; |
13653 | 0 | size_t idx_count = written_index_counts[t]; |
13654 | 0 | size_t fc_count = written_face_counts[t]; |
13655 | 0 | if (dst_idx != src_idx && idx_count > 0) { |
13656 | 0 | std::memmove(&attrib.indices[dst_idx], &attrib.indices[src_idx], |
13657 | 0 | idx_count * sizeof(index_t)); |
13658 | 0 | } |
13659 | 0 | if (dst_face != src_face && fc_count > 0) { |
13660 | 0 | std::memmove(&attrib.face_num_verts[dst_face], |
13661 | 0 | &attrib.face_num_verts[src_face], |
13662 | 0 | fc_count * sizeof(int)); |
13663 | 0 | std::memmove(&attrib.material_ids[dst_face], |
13664 | 0 | &attrib.material_ids[src_face], |
13665 | 0 | fc_count * sizeof(int)); |
13666 | 0 | if (any_smoothing) { |
13667 | 0 | std::memmove(&all_smoothing_group_ids[dst_face], |
13668 | 0 | &all_smoothing_group_ids[src_face], |
13669 | 0 | fc_count * sizeof(unsigned int)); |
13670 | 0 | } |
13671 | 0 | } |
13672 | 0 | dst_idx += idx_count; |
13673 | 0 | dst_face += fc_count; |
13674 | 0 | } |
13675 | 0 | } |
13676 | |
|
13677 | 0 | attrib.indices.truncate(actual_num_indices); |
13678 | 0 | attrib.face_num_verts.truncate(actual_num_faces); |
13679 | 0 | attrib.material_ids.truncate(actual_num_faces); |
13680 | 0 | if (any_smoothing) { |
13681 | 0 | all_smoothing_group_ids.truncate(actual_num_faces); |
13682 | 0 | attrib.smoothing_group_ids = all_smoothing_group_ids; |
13683 | 0 | } |
13684 | |
|
13685 | 0 | opt_appendOutOfBoundsWarnings( |
13686 | 0 | warn, greatest_v_idx, greatest_vn_idx, greatest_vt_idx, |
13687 | 0 | static_cast<int>(attrib.vertices.size() / 3), |
13688 | 0 | static_cast<int>(attrib.normals.size() / 3), |
13689 | 0 | static_cast<int>(attrib.texcoords.size() / 2), |
13690 | 0 | all_line_infos.size() + 1); |
13691 | 0 | for (size_t deg = 0; deg < eof_pending_degenerate_faces; deg++) { |
13692 | 0 | if (warn) (*warn) += "Degenerated face found\n."; |
13693 | 0 | } |
13694 | | |
13695 | | // ---- Phase 5: construct shape ranges (views, no copies) ---- |
13696 | 0 | { |
13697 | 0 | const size_t total_faces = attrib.face_num_verts.size(); |
13698 | | // Build prefix-sum for index offsets |
13699 | 0 | TypedArray<size_t> idx_prefix; |
13700 | 0 | idx_prefix.allocate(arena, total_faces + 1); |
13701 | 0 | idx_prefix[0] = 0; |
13702 | 0 | for (size_t fi = 0; fi < total_faces; fi++) { |
13703 | 0 | idx_prefix[fi + 1] = |
13704 | 0 | idx_prefix[fi] + static_cast<size_t>(attrib.face_num_verts[fi]); |
13705 | 0 | } |
13706 | |
|
13707 | 0 | size_t face_count = 0; |
13708 | 0 | std::string current_name; |
13709 | 0 | size_t face_prev_offset = 0; |
13710 | 0 | bool have_active_shape_name = false; |
13711 | |
|
13712 | 0 | auto emit_shape = [&](const std::string &name, size_t f_start, size_t f_end) { |
13713 | 0 | if (f_end <= f_start) return; |
13714 | 0 | OptShapeRange sr; |
13715 | 0 | sr.name = name; |
13716 | 0 | sr.face_offset = f_start; |
13717 | 0 | sr.face_count = f_end - f_start; |
13718 | 0 | sr.index_offset = idx_prefix[f_start]; |
13719 | 0 | sr.index_count = idx_prefix[f_end] - idx_prefix[f_start]; |
13720 | 0 | result->shapes.push_back(std::move(sr)); |
13721 | 0 | }; |
13722 | |
|
13723 | 0 | for (size_t t = 0; t < num_t; t++) { |
13724 | 0 | const OptThreadData &td = thread_data[t]; |
13725 | 0 | for (size_t si = 0; si < td.seq.size(); si++) { |
13726 | 0 | const OptSeqEntry &entry = td.seq[si]; |
13727 | 0 | if (entry.kind == OptSeqEntry::SEQ_META) { |
13728 | 0 | const OptMetaCmd &meta = td.metas[entry.index]; |
13729 | 0 | if (meta.type == OPT_CMD_O || meta.type == OPT_CMD_G) { |
13730 | 0 | std::string name; |
13731 | 0 | if (meta.type == OPT_CMD_O && meta.str_ptr) { |
13732 | 0 | name.assign(meta.str_ptr, meta.str_len); |
13733 | 0 | } else if (!meta.str_storage.empty()) { |
13734 | 0 | name = meta.str_storage; |
13735 | 0 | } else if (meta.str_ptr) { |
13736 | 0 | name.assign(meta.str_ptr, meta.str_len); |
13737 | 0 | } |
13738 | 0 | while (!name.empty() && |
13739 | 0 | (name.back() == '\r' || name.back() == '\n')) |
13740 | 0 | name.pop_back(); |
13741 | |
|
13742 | 0 | if (face_count == 0) { |
13743 | 0 | current_name = name; |
13744 | 0 | face_prev_offset = 0; |
13745 | 0 | have_active_shape_name = true; |
13746 | 0 | } else { |
13747 | 0 | if (!have_active_shape_name) { |
13748 | 0 | emit_shape(std::string(), 0, face_count); |
13749 | 0 | } else if (face_count > face_prev_offset) { |
13750 | 0 | emit_shape(current_name, face_prev_offset, face_count); |
13751 | 0 | } |
13752 | 0 | current_name = name; |
13753 | 0 | face_prev_offset = face_count; |
13754 | 0 | have_active_shape_name = true; |
13755 | 0 | } |
13756 | 0 | } |
13757 | 0 | } else { |
13758 | | // SEQ_FACE |
13759 | 0 | face_count += command_written_faces[t][entry.index]; |
13760 | 0 | } |
13761 | 0 | } |
13762 | 0 | } |
13763 | | |
13764 | | // Final shape |
13765 | 0 | if (face_count > face_prev_offset || face_count == 0) { |
13766 | 0 | emit_shape(current_name, face_prev_offset, face_count); |
13767 | 0 | } |
13768 | 0 | } |
13769 | |
|
13770 | 0 | result->valid = true; |
13771 | 0 | return true; |
13772 | 0 | } |
13773 | | |
13774 | | // ---- LoadObjOptTyped (buffer version, public API) ---- |
13775 | | |
13776 | | OptResult LoadObjOptTyped(const char *buf, size_t buf_len, |
13777 | | std::string *warn, std::string *err, |
13778 | 0 | const OptLoadConfig &config) { |
13779 | 0 | OptResult result; |
13780 | 0 | LoadObjOptTyped_internal(&result, warn, err, buf, buf_len, |
13781 | 0 | std::string(), "<stream>", false, config); |
13782 | 0 | return result; |
13783 | 0 | } |
13784 | | |
13785 | | // ---- LoadObjOptTyped (file version) ---- |
13786 | | |
13787 | | OptResult LoadObjOptTyped(const char *filename, |
13788 | | std::string *warn, std::string *err, |
13789 | | const char *mtl_basedir, |
13790 | 0 | const OptLoadConfig &config) { |
13791 | 0 | OptResult result; |
13792 | 0 | if (!filename) { |
13793 | 0 | if (err) *err = "filename is null."; |
13794 | 0 | return result; |
13795 | 0 | } |
13796 | | |
13797 | 0 | std::string filepath(filename); |
13798 | 0 | std::string baseDir; |
13799 | 0 | if (mtl_basedir) { |
13800 | 0 | baseDir = mtl_basedir; |
13801 | 0 | } else { |
13802 | 0 | size_t pos = filepath.find_last_of("/\\"); |
13803 | 0 | if (pos != std::string::npos) { |
13804 | 0 | baseDir = filepath.substr(0, pos + 1); |
13805 | 0 | } |
13806 | 0 | } |
13807 | 0 | if (!baseDir.empty()) { |
13808 | 0 | #ifndef _WIN32 |
13809 | 0 | const char dirsep = '/'; |
13810 | | #else |
13811 | | const char dirsep = '\\'; |
13812 | | #endif |
13813 | 0 | if (baseDir[baseDir.length() - 1] != dirsep) baseDir += dirsep; |
13814 | 0 | } |
13815 | |
|
13816 | | #ifdef TINYOBJLOADER_USE_MMAP |
13817 | | { |
13818 | | MappedFile mf; |
13819 | | if (mf.open(filepath.c_str())) { |
13820 | | LoadObjOptTyped_internal(&result, warn, err, mf.data, mf.size, |
13821 | | baseDir, filepath, true, config); |
13822 | | return result; |
13823 | | } |
13824 | | } |
13825 | | #endif |
13826 | |
|
13827 | | #ifdef _WIN32 |
13828 | | std::ifstream ifs(LongPathW(UTF8ToWchar(filepath)).c_str(), |
13829 | | std::ios::binary | std::ios::ate); |
13830 | | #else |
13831 | 0 | std::ifstream ifs(filepath.c_str(), std::ios::binary | std::ios::ate); |
13832 | 0 | #endif |
13833 | 0 | if (!ifs.is_open()) { |
13834 | 0 | if (err) *err = "Cannot open file: " + filepath; |
13835 | 0 | return result; |
13836 | 0 | } |
13837 | | |
13838 | 0 | std::streamsize fsize = ifs.tellg(); |
13839 | 0 | ifs.seekg(0, std::ios::beg); |
13840 | |
|
13841 | 0 | if (fsize <= 0) { |
13842 | 0 | LoadObjOptTyped_internal(&result, warn, err, "", 0, |
13843 | 0 | baseDir, filepath, true, config); |
13844 | 0 | return result; |
13845 | 0 | } |
13846 | | |
13847 | 0 | std::vector<char> file_buf(static_cast<size_t>(fsize)); |
13848 | 0 | if (!ifs.read(file_buf.data(), fsize)) { |
13849 | 0 | if (err) *err = "Failed to read file: " + filepath; |
13850 | 0 | return result; |
13851 | 0 | } |
13852 | 0 | ifs.close(); |
13853 | |
|
13854 | 0 | LoadObjOptTyped_internal(&result, warn, err, file_buf.data(), |
13855 | 0 | static_cast<size_t>(fsize), baseDir, filepath, |
13856 | 0 | true, config); |
13857 | 0 | return result; |
13858 | 0 | } |
13859 | | |
13860 | | #ifdef __clang__ |
13861 | | #pragma clang diagnostic pop |
13862 | | #endif |
13863 | | } // namespace tinyobj |
13864 | | |
13865 | | #endif |