1 /* 2 * Copyright (C) 2020 Linux Studio Plugins Project <https://lsp-plug.in/> 3 * (C) 2020 Vladimir Sadovnikov <sadko4u@gmail.com> 4 * 5 * This file is part of lsp-plugins 6 * Created on: 21 апр. 2017 г. 7 * 8 * lsp-plugins is free software: you can redistribute it and/or modify 9 * it under the terms of the GNU Lesser General Public License as published by 10 * the Free Software Foundation, either version 3 of the License, or 11 * any later version. 12 * 13 * lsp-plugins is distributed in the hope that it will be useful, 14 * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 * GNU Lesser General Public License for more details. 17 * 18 * You should have received a copy of the GNU Lesser General Public License 19 * along with lsp-plugins. If not, see <https://www.gnu.org/licenses/>. 20 */ 21 22 #include <stdio.h> 23 #include <errno.h> 24 #include <string.h> 25 #include <unistd.h> 26 #include <stdlib.h> 27 #include <locale.h> 28 29 #include <core/debug.h> 30 #include <core/files/3d/Parser.h> 31 #include <core/io/InSequence.h> 32 33 #define IO_BUF_SIZE 8192 34 35 namespace lsp 36 { 37 namespace obj 38 { is_space(char ch)39 inline bool Parser::is_space(char ch) 40 { 41 return (ch == ' ') || (ch == '\t'); 42 } 43 prefix_match(const char * s,const char * prefix)44 inline bool Parser::prefix_match(const char *s, const char *prefix) 45 { 46 while (*prefix != '\0') 47 { 48 if (*(s++) != *(prefix++)) 49 return false; 50 } 51 return is_space(*s); 52 } 53 parse_float(float * dst,const char ** s)54 bool Parser::parse_float(float *dst, const char **s) 55 { 56 if (*s == NULL) 57 return false; 58 59 errno = 0; 60 char *ptr = NULL; 61 float result = strtof(*s, &ptr); 62 if ((errno != 0) || (ptr == *s)) 63 return false; 64 *dst = result; 65 *s = ptr; 66 return true; 67 } 68 parse_int(ssize_t * dst,const char ** s)69 bool Parser::parse_int(ssize_t *dst, const char **s) 70 { 71 if ((*s == NULL) || (**s == '\0') || (**s == ' ')) 72 return false; 73 74 errno = 0; 75 char *ptr = NULL; 76 long result = strtol(*s, &ptr, 10); 77 if ((errno != 0) || (ptr == *s)) 78 return false; 79 *dst = result; 80 *s = ptr; 81 return true; 82 } 83 skip_spaces(const char * s)84 const char *Parser::skip_spaces(const char *s) 85 { 86 if (s == NULL) 87 return NULL; 88 89 while (true) 90 { 91 char ch = *s; 92 if ((ch == '\0') || (!is_space(ch))) 93 return s; 94 s++; 95 } 96 } 97 end_of_line(const char * s)98 bool Parser::end_of_line(const char *s) 99 { 100 if (s == NULL) 101 return true; 102 103 while (true) 104 { 105 char ch = *s; 106 if (ch == '\0') 107 return true; 108 if (!is_space(ch)) 109 return false; 110 s++; 111 } 112 } 113 eliminate_comments(LSPString * s)114 void Parser::eliminate_comments(LSPString *s) 115 { 116 size_t len = s->length(), r=0, w=0; 117 bool slash = false; 118 119 while (r < len) 120 { 121 lsp_wchar_t ch = s->char_at(r); 122 if (slash) 123 { 124 ++r; 125 if ((ch != '#') && (ch != '\\')) 126 s->set_at(w++, '\\'); 127 s->set_at(w++, ch); 128 slash = false; 129 continue; 130 } 131 else if (ch == '#') 132 { 133 s->set_length(r); 134 return; 135 } 136 else if (ch == '\\') 137 { 138 slash = true; 139 ++r; 140 continue; 141 } 142 143 if (r != w) 144 s->set_at(w, ch); 145 ++r, ++w; 146 } 147 148 if (slash) 149 s->set_at(w++, '\\'); 150 s->set_length(w); 151 } 152 read_line(file_buffer_t * fb)153 status_t Parser::read_line(file_buffer_t *fb) 154 { 155 // Clear previous line contents 156 fb->line.clear(); 157 158 while (true) 159 { 160 // Ensure that there is data in buffer 161 if (fb->off >= fb->len) 162 { 163 // No data in the buffer, read from input stream 164 ssize_t n = fb->in->read(fb->data, IO_BUF_SIZE); 165 if (n <= 0) 166 { 167 if (n != -STATUS_EOF) 168 return -n; 169 return (fb->line.length() > 0) ? STATUS_OK : STATUS_EOF; 170 } 171 fb->len = n; 172 fb->off = 0; 173 } 174 175 // Scan for line ending 176 if (fb->skip_wc) 177 { 178 fb->skip_wc = false; 179 if (fb->data[fb->off] == '\r') 180 { 181 ++fb->off; 182 continue; 183 } 184 } 185 186 // Scan for line ending character 187 size_t tail = fb->off; 188 while (tail < fb->len) 189 { 190 lsp_wchar_t ch = fb->data[tail++]; 191 if (ch == '\n') // Found! 192 { 193 fb->skip_wc = true; 194 break; 195 } 196 } 197 198 // Append data to string and update buffer state 199 fb->line.append(&fb->data[fb->off], tail - fb->off); 200 fb->off = tail; 201 202 // Now analyze last string character 203 size_t len = fb->line.length(); 204 if (fb->line.last() != '\n') // Not end of line? 205 continue; 206 fb->line.set_length(--len); 207 208 // Compute number of terminating '\\' characters 209 ssize_t slashes = 0, xoff = len-1; 210 while ((xoff >= 0) && (fb->line.char_at(xoff) == '\\')) 211 { 212 ++slashes; 213 --xoff; 214 } 215 216 // Line has been split into multiple lines? 217 if (slashes & 1) 218 { 219 fb->line.set_length(--len); 220 continue; 221 } 222 223 // Alright, now we have complete line and can return it 224 eliminate_comments(&fb->line); 225 return STATUS_OK; 226 } 227 } 228 parse_lines(file_buffer_t * fb,IObjHandler * handler)229 status_t Parser::parse_lines(file_buffer_t *fb, IObjHandler *handler) 230 { 231 status_t result = STATUS_OK; 232 233 parse_state_t state; 234 state.pHandler = handler; 235 state.nObjectID = -1; 236 state.nPointID = 0; 237 state.nFaceID = 0; 238 state.nLineID = 0; 239 state.nLines = 0; 240 241 while (true) 242 { 243 // Try to read line 244 result = read_line(fb); 245 if (result != STATUS_OK) 246 { 247 if (result == STATUS_EOF) 248 result = parse_finish(&state); 249 break; 250 } 251 252 // Check that line is not empty 253 const char *l = skip_spaces(fb->line.get_utf8()); 254 if ((l == NULL) || (*l == '\0')) 255 continue; 256 257 // Parse line 258 result = parse_line(&state, l); 259 if (result != STATUS_OK) 260 break; 261 } 262 263 // Destroy state 264 state.sVx.flush(); 265 state.sParVx.flush(); 266 state.sTexVx.flush(); 267 state.sNorm.flush(); 268 269 state.sVxIdx.flush(); 270 state.sTexVxIdx.flush(); 271 state.sNormIdx.flush(); 272 273 return result; 274 } 275 parse(const char * path,IObjHandler * handler)276 status_t Parser::parse(const char *path, IObjHandler *handler) 277 { 278 if ((path == NULL) || (handler == NULL)) 279 return STATUS_BAD_ARGUMENTS; 280 281 LSPString spath; 282 if (!spath.set_utf8(path)) 283 return STATUS_NO_MEM; 284 285 return parse(&spath, handler); 286 } 287 parse(const LSPString * path,IObjHandler * handler)288 status_t Parser::parse(const LSPString *path, IObjHandler *handler) 289 { 290 if ((path == NULL) || (handler == NULL)) 291 return STATUS_BAD_ARGUMENTS; 292 293 io::InSequence in; 294 status_t res = in.open(path, "UTF-8"); 295 if (res != STATUS_OK) 296 return res; 297 298 // Initialize file buffer 299 file_buffer_t fb; 300 fb.in = ∈ 301 fb.len = 0; 302 fb.off = 0; 303 fb.skip_wc = false; 304 fb.data = reinterpret_cast<lsp_wchar_t *>(::malloc(IO_BUF_SIZE * sizeof(lsp_wchar_t))); 305 if (fb.data == NULL) 306 { 307 in.close(); 308 return STATUS_NO_MEM; 309 } 310 311 char *saved_locale = setlocale(LC_NUMERIC, "C"); 312 status_t result = parse_lines(&fb, handler); 313 setlocale(LC_NUMERIC, saved_locale); 314 315 // Destroy buffer data 316 ::free(fb.data); 317 in.close(); 318 319 return result; 320 } 321 parse(const io::Path * path,IObjHandler * handler)322 status_t Parser::parse(const io::Path *path, IObjHandler *handler) 323 { 324 if ((path == NULL) || (handler == NULL)) 325 return STATUS_BAD_ARGUMENTS; 326 return parse(path->as_string(), handler); 327 } 328 parse_line(parse_state_t * st,const char * s)329 status_t Parser::parse_line(parse_state_t *st, const char *s) 330 { 331 // lsp_trace("%s", s); 332 status_t result = ((st->nLines++) > 0) ? STATUS_CORRUPTED_FILE : STATUS_BAD_FORMAT; 333 334 switch (*(s++)) 335 { 336 case 'b': // bmat, bevel 337 if (prefix_match(s, "mat")) // bmat 338 return STATUS_OK; 339 else if (prefix_match(s, "evel")) // bevel 340 return STATUS_OK; 341 break; 342 343 case 'c': // cstype, curv, curv2, con, c_interp, ctech 344 if (prefix_match(s, "stype")) // cstype 345 return STATUS_OK; 346 else if (prefix_match(s, "urv")) // curv 347 return STATUS_OK; 348 else if (prefix_match(s, "urv2")) // curv2 349 return STATUS_OK; 350 else if (prefix_match(s, "on")) // con 351 return STATUS_OK; 352 else if (prefix_match(s, "_interp")) // c_interp 353 return STATUS_OK; 354 else if (prefix_match(s, "tech")) // ctech 355 return STATUS_OK; 356 break; 357 358 case 'd': // deg, d_interp 359 if (prefix_match(s, "eg")) // deg 360 return STATUS_OK; 361 else if (prefix_match(s, "_interp")) // d_interp 362 return STATUS_OK; 363 break; 364 365 case 'e': // end 366 if (prefix_match(s, "nd")) // end 367 return STATUS_OK; 368 break; 369 370 case 'f': // f 371 if (is_space(*s)) // f - face 372 { 373 // Clear previously used lists 374 st->sVxIdx.clear(); 375 st->sTexVxIdx.clear(); 376 st->sNormIdx.clear(); 377 378 // Parse face 379 while (true) 380 { 381 ssize_t v = 0, vt = 0, vn = 0; 382 383 // Parse indexes 384 s = skip_spaces(s); 385 if (!parse_int(&v, &s)) 386 break; 387 if (*s == '/') 388 { 389 ++s; 390 if (!parse_int(&vt, &s)) 391 vt = 0; 392 if (*s == '/') 393 { 394 ++s; 395 if (!parse_int(&vn, &s)) 396 vn = 0; 397 } 398 } 399 400 // Ensure that indexes are correct 401 v = (v < 0) ? st->sVx.size() + v : v - 1; 402 if ((v < 0) || (v >= ssize_t(st->sVx.size()))) 403 return result; 404 405 vt = (vt < 0) ? st->sTexVx.size() + vt : vt - 1; 406 if ((vt < -1) || (vt >= ssize_t(st->sTexVx.size()))) 407 return result; 408 409 vn = (vn < 0) ? st->sNorm.size() + vn : vn - 1; 410 if ((vn < -1) || (vn >= ssize_t(st->sNorm.size()))) 411 return result; 412 413 // Register vertex 414 ofp_point3d_t *xp = st->sVx.at(v); 415 if (xp->oid != st->nObjectID) 416 { 417 xp->oid = st->nObjectID; 418 xp->idx = st->pHandler->add_vertex(xp); 419 if (xp->idx < 0) 420 return -xp->idx; 421 } 422 v = xp->idx; 423 424 // Register texture vertex 425 if (vt >= 0) 426 { 427 xp = st->sTexVx.at(vt); 428 if (xp->oid != st->nObjectID) 429 { 430 xp->oid = st->nObjectID; 431 xp->idx = st->pHandler->add_texture_vertex(xp); 432 if (xp->idx < 0) 433 return -xp->idx; 434 } 435 vt = xp->idx; 436 } 437 438 // Register normal vector 439 if (vn >= 0) 440 { 441 ofp_vector3d_t *xn = st->sNorm.at(vn); 442 if (xn == NULL) 443 return STATUS_BAD_FORMAT; 444 vn = xn->idx; 445 } 446 447 // Add items to lists 448 if (!st->sVxIdx.add(v)) 449 return STATUS_NO_MEM; 450 if (!st->sTexVxIdx.add(vt)) 451 return STATUS_NO_MEM; 452 if (!st->sNormIdx.add(vn)) 453 return STATUS_NO_MEM; 454 } 455 456 if (!end_of_line(s)) 457 return result; 458 459 // Check face parameters 460 if (st->sVxIdx.size() < 3) 461 return STATUS_BAD_FORMAT; 462 463 // Call parser to handle data 464 result = st->pHandler->add_face(st->sVxIdx.get_array(), st->sNormIdx.get_array(), st->sTexVxIdx.get_array(), st->sVxIdx.size()); 465 } 466 break; 467 468 case 'g': // g 469 if (is_space(*s)) // g 470 return STATUS_OK; 471 break; 472 473 case 'h': // hole 474 if (prefix_match(s, "ole")) // hole 475 return STATUS_OK; 476 break; 477 478 case 'l': // l, lod 479 if (is_space(*s)) // l - line 480 { 481 // Clear previously used lists 482 st->sVxIdx.clear(); 483 st->sTexVxIdx.clear(); 484 485 // Parse face 486 while (true) 487 { 488 ssize_t v = 0, vt = 0; 489 490 // Parse indexes 491 s = skip_spaces(s); 492 if (!parse_int(&v, &s)) 493 break; 494 if (*(s++) != '/') 495 return result; 496 if (!parse_int(&vt, &s)) 497 vt = 0; 498 499 // Ensure that indexes are correct 500 v = (v < 0) ? st->sVx.size() + v : v - 1; 501 if ((v < 0) || (v >= ssize_t(st->sVx.size()))) 502 return result; 503 504 vt = (vt < 0) ? st->sTexVx.size() + vt : vt - 1; 505 if ((vt <= -1) || (vt >= ssize_t(st->sTexVx.size()))) 506 return result; 507 508 // Register vertex 509 ofp_point3d_t *xp = st->sVx.at(v); 510 if (xp->oid != st->nObjectID) 511 { 512 xp->oid = st->nObjectID; 513 xp->idx = st->pHandler->add_vertex(xp); 514 if (xp->idx < 0) 515 return -xp->idx; 516 } 517 v = xp->idx; 518 519 // Register texture vertex 520 if (vt >= 0) 521 { 522 xp = st->sTexVx.at(vt); 523 if (xp->oid != st->nObjectID) 524 { 525 xp->oid = st->nObjectID; 526 xp->idx = st->pHandler->add_texture_vertex(xp); 527 if (xp->idx < 0) 528 return -xp->idx; 529 } 530 vt = xp->idx; 531 } 532 533 // Add items to lists 534 if (!st->sVxIdx.add(&v)) 535 return STATUS_NO_MEM; 536 if (!st->sTexVxIdx.add(&vt)) 537 return STATUS_NO_MEM; 538 } 539 540 if (!end_of_line(s)) 541 return result; 542 543 // Check line parameters 544 if (st->sVxIdx.size() < 2) 545 return STATUS_BAD_FORMAT; 546 547 // Call parser to handle data 548 result = st->pHandler->add_line(st->sVxIdx.get_array(), st->sTexVxIdx.get_array(), st->sVxIdx.size()); 549 } 550 else if (prefix_match(s, "od")) // lod 551 return STATUS_OK; 552 break; 553 554 case 'm': // mg, mtllib 555 if (prefix_match(s, "g")) // mg 556 return STATUS_OK; 557 else if (prefix_match(s, "tllib")) // mtllib 558 return STATUS_OK; 559 break; 560 561 case 'o': // o 562 if (is_space(*s)) // o 563 { 564 s = skip_spaces(s+1); 565 if (st->nObjectID >= 0) 566 { 567 result = st->pHandler->end_object(st->nObjectID); 568 if (result != STATUS_OK) 569 return result; 570 } 571 result = st->pHandler->begin_object(++st->nObjectID, s); 572 } 573 break; 574 575 case 'p': // p, parm 576 if (is_space(*s)) // p 577 { 578 st->sVxIdx.clear(); 579 580 // Parse point 581 while (true) 582 { 583 ssize_t v = 0; 584 585 // Parse indexes 586 s = skip_spaces(s); 587 if (!parse_int(&v, &s)) 588 break; 589 590 // Ensure that indexes are correct 591 v = (v < 0) ? st->sVx.size() + v : v - 1; 592 if ((v < 0) || (v >= ssize_t(st->sVx.size()))) 593 return result; 594 595 // Register vertex 596 ofp_point3d_t *xp = st->sVx.at(v); 597 if (xp->oid != st->nObjectID) 598 { 599 xp->oid = st->nObjectID; 600 xp->idx = st->pHandler->add_vertex(xp); 601 if (xp->idx < 0) 602 return -xp->idx; 603 } 604 v = xp->idx; 605 606 // Add items to lists 607 if (!st->sVxIdx.add(&v)) 608 return STATUS_NO_MEM; 609 } 610 611 // Check that we reached end of line 612 if (!end_of_line(s)) 613 return result; 614 615 result = st->pHandler->add_points(st->sVxIdx.get_array(), st->sVxIdx.size()); 616 } 617 else if (prefix_match(s, "arm")) // parm 618 return STATUS_OK; 619 break; 620 621 case 's': // s, step, surf, scrv, sp, shadow_obj, stech 622 if (is_space(*s)) // s 623 return STATUS_OK; 624 else if (prefix_match(s, "tep")) // step 625 return STATUS_OK; 626 else if (prefix_match(s, "urf")) // surf 627 return STATUS_OK; 628 else if (prefix_match(s, "rcv")) // srcv 629 return STATUS_OK; 630 else if (prefix_match(s, "p")) // sp 631 return STATUS_OK; 632 else if (prefix_match(s, "hadow_obj")) // shadow_obj 633 return STATUS_OK; 634 else if (prefix_match(s, "tech")) // stech 635 return STATUS_OK; 636 break; 637 638 case 't': // trim, trace_obj 639 if (prefix_match(s, "rim")) // trim 640 return STATUS_OK; 641 else if (prefix_match(s, "race_obj")) // trace_obj 642 return STATUS_OK; 643 break; 644 645 case 'u': // usemtl 646 if (prefix_match(s, "semtl")) // usemtl 647 return STATUS_OK; 648 break; 649 650 case 'v': // v, vt, vn, vp 651 if (is_space(*s)) // v 652 { 653 ofp_point3d_t p; 654 655 s = skip_spaces(s+1); 656 if (!parse_float(&p.x, &s)) 657 return result; 658 s = skip_spaces(s); 659 if (!parse_float(&p.y, &s)) 660 return result; 661 s = skip_spaces(s); 662 if (!parse_float(&p.z, &s)) 663 p.z = 0.0f; // Extension, strictly required in obj format, for our case facilitated 664 s = skip_spaces(s); 665 if (!parse_float(&p.w, &s)) 666 p.w = 1.0f; 667 668 if (!end_of_line(s)) 669 return result; 670 671 p.oid = -1; 672 p.idx = -1; 673 if (!st->sVx.add(&p)) 674 return STATUS_NO_MEM; 675 result = STATUS_OK; 676 } 677 else if (prefix_match(s, "n")) // vn 678 { 679 ofp_vector3d_t v; 680 681 s = skip_spaces(s+2); 682 if (!parse_float(&v.dx, &s)) 683 return result; 684 s = skip_spaces(s); 685 if (!parse_float(&v.dy, &s)) 686 return result; 687 s = skip_spaces(s); 688 if (!parse_float(&v.dz, &s)) 689 v.dz = 0.0f; // Extension, strictly required in obj format, for our case facilitated 690 v.dw = 0.0f; 691 692 if (!end_of_line(s)) 693 return result; 694 695 v.oid = -1; 696 v.idx = st->pHandler->add_normal(&v); 697 if (v.idx < 0) 698 return -v.idx; 699 if (!st->sNorm.add(&v)) 700 return STATUS_NO_MEM; 701 result = STATUS_OK; 702 } 703 else if (prefix_match(s, "p")) // vp 704 { 705 ofp_point3d_t p; 706 707 s = skip_spaces(s+2); 708 if (parse_float(&p.x, &s)) 709 return result; 710 s = skip_spaces(s); 711 if (!parse_float(&p.y, &s)) 712 p.y = 0.0f; 713 p.z = 0.0f; 714 s = skip_spaces(s); 715 if (!parse_float(&p.w, &s)) 716 p.w = 1.0f; 717 718 if (!end_of_line(s)) 719 return result; 720 721 p.oid = -1; 722 p.idx = -1; 723 if (!st->sParVx.add(&p)) 724 return STATUS_NO_MEM; 725 result = STATUS_OK; 726 } 727 else if (prefix_match(s, "t")) // vt 728 { 729 ofp_point3d_t p; 730 731 s = skip_spaces(s+2); 732 if (!parse_float(&p.x, &s)) 733 return result; 734 s = skip_spaces(s); 735 if (!parse_float(&p.y, &s)) 736 p.y = 0.0f; 737 p.z = 0.0f; 738 s = skip_spaces(s); 739 if (!parse_float(&p.w, &s)) 740 p.w = 0.0f; 741 742 if (!end_of_line(s)) 743 return result; 744 745 p.oid = -1; 746 p.idx = -1; 747 if (!st->sTexVx.add(&p)) 748 return STATUS_NO_MEM; 749 result = STATUS_OK; 750 } 751 break; 752 } 753 754 return result; 755 } 756 parse_finish(parse_state_t * st)757 status_t Parser::parse_finish(parse_state_t *st) 758 { 759 status_t result = STATUS_OK; 760 761 if (st->nObjectID >= 0) 762 { 763 result = st->pHandler->end_object(st->nObjectID); 764 if (result != STATUS_OK) 765 return result; 766 } 767 768 if (result == STATUS_OK) 769 result = st->pHandler->end_of_data(); 770 771 return result; 772 } 773 } 774 } /* namespace lsp */ 775