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