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       = &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