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 #ifndef FAST_OBJ_HDR
28 #define FAST_OBJ_HDR
29 
30 
31 typedef struct
32 {
33     /* Texture name from .mtl file */
34     char*                       name;
35 
36     /* Resolved path to texture */
37     char*                       path;
38 
39 } fastObjTexture;
40 
41 
42 typedef struct
43 {
44     /* Material name */
45     char*                       name;
46 
47     /* Parameters */
48     float                       Ka[3];  /* Ambient */
49     float                       Kd[3];  /* Diffuse */
50     float                       Ks[3];  /* Specular */
51     float                       Ke[3];  /* Emission */
52     float                       Kt[3];  /* Transmittance */
53     float                       Ns;     /* Shininess */
54     float                       Ni;     /* Index of refraction */
55     float                       Tf[3];  /* Transmission filter */
56     float                       d;      /* Disolve (alpha) */
57     int                         illum;  /* Illumination model */
58 
59     /* Texture maps */
60     fastObjTexture              map_Ka;
61     fastObjTexture              map_Kd;
62     fastObjTexture              map_Ks;
63     fastObjTexture              map_Ke;
64     fastObjTexture              map_Kt;
65     fastObjTexture              map_Ns;
66     fastObjTexture              map_Ni;
67     fastObjTexture              map_d;
68     fastObjTexture              map_bump;
69 
70 
71 } fastObjMaterial;
72 
73 
74 typedef struct
75 {
76     unsigned int                p;
77     unsigned int                t;
78     unsigned int                n;
79 
80 } fastObjIndex;
81 
82 
83 typedef struct
84 {
85     /* Group name */
86     char*                       name;
87 
88     /* Number of faces */
89     unsigned int                face_count;
90 
91     /* First face in fastObjMesh face_* arrays */
92     unsigned int                face_offset;
93 
94     /* First index in fastObjMesh indices array */
95     unsigned int                index_offset;
96 
97 } fastObjGroup;
98 
99 
100 typedef struct
101 {
102     /* Vertex data */
103     unsigned int                position_count;
104     float*                      positions;
105 
106     unsigned int                texcoord_count;
107     float*                      texcoords;
108 
109     unsigned int                normal_count;
110     float*                      normals;
111 
112     /* Face data: one element for each face */
113     unsigned int                face_count;
114     unsigned int*               face_vertices;
115     unsigned int*               face_materials;
116 
117     /* Index data: one element for each face vertex */
118     fastObjIndex*               indices;
119 
120     /* Materials */
121     unsigned int                material_count;
122     fastObjMaterial*            materials;
123 
124     /* Mesh groups */
125     unsigned int                group_count;
126     fastObjGroup*               groups;
127 
128 } fastObjMesh;
129 
130 
131 fastObjMesh*                    fast_obj_read(const char* path);
132 void                            fast_obj_destroy(fastObjMesh* mesh);
133 
134 #endif
135 
136 
137 #ifdef FAST_OBJ_IMPLEMENTATION
138 
139 #include <stdio.h>
140 #include <stdlib.h>
141 #include <string.h>
142 
143 #ifndef FAST_OBJ_REALLOC
144 #define FAST_OBJ_REALLOC        realloc
145 #endif
146 
147 #ifndef FAST_OBJ_FREE
148 #define FAST_OBJ_FREE           free
149 #endif
150 
151 #ifdef _WIN32
152 #define FAST_OBJ_SEPARATOR      '\\'
153 #define FAST_OBJ_OTHER_SEP      '/'
154 #else
155 #define FAST_OBJ_SEPARATOR      '/'
156 #define FAST_OBJ_OTHER_SEP      '\\'
157 #endif
158 
159 
160 /* Size of buffer to read into */
161 #define BUFFER_SIZE             65536
162 
163 /* Max supported power when parsing float */
164 #define MAX_POWER               20
165 
166 typedef struct
167 {
168     /* Final mesh */
169     fastObjMesh*                mesh;
170 
171     /* Current group */
172     fastObjGroup                group;
173 
174     /* Current material index */
175     unsigned int                material;
176 
177     /* Current line in file */
178     unsigned int                line;
179 
180     /* Base path for materials/textures */
181     char*                       base;
182 
183 } fastObjData;
184 
185 
186 static const
187 double POWER_10_POS[MAX_POWER] =
188 {
189     1.0e0,  1.0e1,  1.0e2,  1.0e3,  1.0e4,  1.0e5,  1.0e6,  1.0e7,  1.0e8,  1.0e9,
190     1.0e10, 1.0e11, 1.0e12, 1.0e13, 1.0e14, 1.0e15, 1.0e16, 1.0e17, 1.0e18, 1.0e19,
191 };
192 
193 static const
194 double POWER_10_NEG[MAX_POWER] =
195 {
196     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,
197     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,
198 };
199 
200 
201 static
memory_realloc(void * ptr,size_t bytes)202 void* memory_realloc(void* ptr, size_t bytes)
203 {
204     return FAST_OBJ_REALLOC(ptr, bytes);
205 }
206 
207 
208 static
memory_dealloc(void * ptr)209 void memory_dealloc(void* ptr)
210 {
211     FAST_OBJ_FREE(ptr);
212 }
213 
214 
215 #define array_clean(_arr)       ((_arr) ? memory_dealloc(_array_header(_arr)), 0 : 0)
216 #define array_push(_arr, _val)  (_array_mgrow(_arr, 1) ? ((_arr)[_array_size(_arr)++] = (_val), _array_size(_arr) - 1) : 0)
217 #define array_size(_arr)        ((_arr) ? _array_size(_arr) : 0)
218 #define array_capacity(_arr)    ((_arr) ? _array_capacity(_arr) : 0)
219 #define array_empty(_arr)       (array_size(_arr) == 0)
220 
221 #define _array_header(_arr)     ((unsigned int*)(_arr) - 2)
222 #define _array_size(_arr)       (_array_header(_arr)[0])
223 #define _array_capacity(_arr)   (_array_header(_arr)[1])
224 #define _array_ngrow(_arr, _n)  ((_arr) == 0 || (_array_size(_arr) + (_n) >= _array_capacity(_arr)))
225 #define _array_mgrow(_arr, _n)  (_array_ngrow(_arr, _n) ? (_array_grow(_arr, _n) != 0) : 1)
226 #define _array_grow(_arr, _n)   (*((void**)&(_arr)) = array_realloc(_arr, _n, sizeof(*(_arr))))
227 
228 
229 static
array_realloc(void * ptr,unsigned int n,unsigned int b)230 void* array_realloc(void* ptr, unsigned int n, unsigned int b)
231 {
232     unsigned int  sz   = array_size(ptr);
233     unsigned int  nsz  = sz + n;
234     unsigned int  cap  = array_capacity(ptr);
235     unsigned int  ncap = 3 * cap / 2;
236     unsigned int* r;
237 
238 
239     if (ncap < nsz)
240         ncap = nsz;
241     ncap = (ncap + 15) & ~15u;
242 
243     r = (unsigned int*)(memory_realloc(ptr ? _array_header(ptr) : 0, b * ncap + 2 * sizeof(unsigned int)));
244     if (!r)
245         return 0;
246 
247     r[0] = sz;
248     r[1] = ncap;
249 
250     return (r + 2);
251 }
252 
253 
254 static
file_open(const char * path)255 void* file_open(const char* path)
256 {
257     return fopen(path, "rb");
258 }
259 
260 
261 static
file_close(void * file)262 void file_close(void* file)
263 {
264     FILE* f;
265 
266     f = (FILE*)(file);
267     fclose(f);
268 }
269 
270 
271 static
file_read(void * file,void * dst,size_t bytes)272 size_t file_read(void* file, void* dst, size_t bytes)
273 {
274     FILE* f;
275 
276     f = (FILE*)(file);
277     return fread(dst, 1, bytes, f);
278 }
279 
280 
281 static
file_size(void * file)282 unsigned long file_size(void* file)
283 {
284     FILE* f;
285     long p;
286     long n;
287 
288     f = (FILE*)(file);
289 
290     p = ftell(f);
291     fseek(f, 0, SEEK_END);
292     n = ftell(f);
293     fseek(f, p, SEEK_SET);
294 
295     if (n > 0)
296         return (unsigned long)(n);
297     else
298         return 0;
299 }
300 
301 
302 static
string_copy(const char * s,const char * e)303 char* string_copy(const char* s, const char* e)
304 {
305     size_t n;
306     char*  p;
307 
308     n = (size_t)(e - s);
309     p = (char*)(memory_realloc(0, n + 1));
310     if (p)
311     {
312         memcpy(p, s, n);
313         p[n] = '\0';
314     }
315 
316     return p;
317 }
318 
319 
320 static
string_substr(const char * s,size_t a,size_t b)321 char* string_substr(const char* s, size_t a, size_t b)
322 {
323     return string_copy(s + a, s + b);
324 }
325 
326 
327 static
string_concat(const char * a,const char * s,const char * e)328 char* string_concat(const char* a, const char* s, const char* e)
329 {
330     size_t an;
331     size_t sn;
332     char*  p;
333 
334     an = a ? strlen(a) : 0;
335     sn = (size_t)(e - s);
336     p = (char*)(memory_realloc(0, an + sn + 1));
337     if (p)
338     {
339         if (a)
340             memcpy(p, a, an);
341         memcpy(p + an, s, sn);
342         p[an + sn] = '\0';
343     }
344 
345     return p;
346 }
347 
348 
349 static
string_equal(const char * a,const char * s,const char * e)350 int string_equal(const char* a, const char* s, const char* e)
351 {
352     while (*a++ == *s++ && s != e)
353         ;
354 
355     return (*a == '\0' && s == e);
356 }
357 
358 
359 static
string_find_last(const char * s,char c,size_t * p)360 int string_find_last(const char* s, char c, size_t* p)
361 {
362     const char* e;
363 
364     e = s + strlen(s);
365     while (e > s)
366     {
367         e--;
368 
369         if (*e == c)
370         {
371             *p = (size_t)(e - s);
372             return 1;
373         }
374     }
375 
376     return 0;
377 }
378 
379 
380 static
string_fix_separators(char * s)381 void string_fix_separators(char* s)
382 {
383     while (*s)
384     {
385         if (*s == FAST_OBJ_OTHER_SEP)
386             *s = FAST_OBJ_SEPARATOR;
387         s++;
388     }
389 }
390 
391 
392 static
is_whitespace(char c)393 int is_whitespace(char c)
394 {
395     return (c == ' ' || c == '\t' || c == '\r');
396 }
397 
398 
399 static
is_newline(char c)400 int is_newline(char c)
401 {
402     return (c == '\n');
403 }
404 
405 
406 static
is_digit(char c)407 int is_digit(char c)
408 {
409     return (c >= '0' && c <= '9');
410 }
411 
412 
413 static
is_exponent(char c)414 int is_exponent(char c)
415 {
416     return (c == 'e' || c == 'E');
417 }
418 
419 
420 static
skip_whitespace(const char * ptr)421 const char* skip_whitespace(const char* ptr)
422 {
423     while (is_whitespace(*ptr))
424         ptr++;
425 
426     return ptr;
427 }
428 
429 
430 static
skip_line(const char * ptr)431 const char* skip_line(const char* ptr)
432 {
433     while (!is_newline(*ptr++))
434         ;
435 
436     return ptr;
437 }
438 
439 
440 static
group_default(void)441 fastObjGroup group_default(void)
442 {
443     fastObjGroup group;
444 
445     group.name         = 0;
446     group.face_count   = 0;
447     group.face_offset  = 0;
448     group.index_offset = 0;
449 
450     return group;
451 }
452 
453 
454 static
group_clean(fastObjGroup * group)455 void group_clean(fastObjGroup* group)
456 {
457     memory_dealloc(group->name);
458 }
459 
460 
461 static
flush_output(fastObjData * data)462 void flush_output(fastObjData* data)
463 {
464     /* Add group if not empty */
465     if (data->group.face_count > 0)
466         array_push(data->mesh->groups, data->group);
467     else
468         group_clean(&data->group);
469 
470     /* Reset for more data */
471     data->group = group_default();
472     data->group.face_offset = array_size(data->mesh->face_vertices);
473     data->group.index_offset = array_size(data->mesh->indices);
474 }
475 
476 
477 static
parse_int(const char * ptr,int * val)478 const char* parse_int(const char* ptr, int* val)
479 {
480     int sign;
481     int num;
482 
483 
484     if (*ptr == '-')
485     {
486         sign = -1;
487         ptr++;
488     }
489     else
490     {
491         sign = +1;
492     }
493 
494     num = 0;
495     while (is_digit(*ptr))
496         num = 10 * num + (*ptr++ - '0');
497 
498     *val = sign * num;
499 
500     return ptr;
501 }
502 
503 
504 static
parse_float(const char * ptr,float * val)505 const char* parse_float(const char* ptr, float* val)
506 {
507     double        sign;
508     double        num;
509     double        fra;
510     double        div;
511     int           eval;
512     const double* powers;
513 
514 
515     ptr = skip_whitespace(ptr);
516 
517     switch (*ptr)
518     {
519     case '+':
520         sign = 1.0;
521         ptr++;
522         break;
523 
524     case '-':
525         sign = -1.0;
526         ptr++;
527         break;
528 
529     default:
530         sign = 1.0;
531         break;
532     }
533 
534 
535     num = 0.0;
536     while (is_digit(*ptr))
537         num = 10.0 * num + (double)(*ptr++ - '0');
538 
539     if (*ptr == '.')
540         ptr++;
541 
542     fra = 0.0;
543     div = 1.0;
544 
545     while (is_digit(*ptr))
546     {
547         fra  = 10.0 * fra + (double)(*ptr++ - '0');
548         div *= 10.0;
549     }
550 
551     num += fra / div;
552 
553     if (is_exponent(*ptr))
554     {
555         ptr++;
556 
557         switch (*ptr)
558         {
559         case '+':
560             powers = POWER_10_POS;
561             ptr++;
562             break;
563 
564         case '-':
565             powers = POWER_10_NEG;
566             ptr++;
567             break;
568 
569         default:
570             powers = POWER_10_POS;
571             break;
572         }
573 
574         eval = 0;
575         while (is_digit(*ptr))
576             eval = 10 * eval + (*ptr++ - '0');
577 
578         num *= (eval >= MAX_POWER) ? 0.0 : powers[eval];
579     }
580 
581     *val = (float)(sign * num);
582 
583     return ptr;
584 }
585 
586 
587 static
parse_vertex(fastObjData * data,const char * ptr)588 const char* parse_vertex(fastObjData* data, const char* ptr)
589 {
590     unsigned int ii;
591     float        v;
592 
593 
594     for (ii = 0; ii < 3; ii++)
595     {
596         ptr = parse_float(ptr, &v);
597         array_push(data->mesh->positions, v);
598     }
599 
600     return ptr;
601 }
602 
603 
604 static
parse_texcoord(fastObjData * data,const char * ptr)605 const char* parse_texcoord(fastObjData* data, const char* ptr)
606 {
607     unsigned int ii;
608     float        v;
609 
610 
611     for (ii = 0; ii < 2; ii++)
612     {
613         ptr = parse_float(ptr, &v);
614         array_push(data->mesh->texcoords, v);
615     }
616 
617     return ptr;
618 }
619 
620 
621 static
parse_normal(fastObjData * data,const char * ptr)622 const char* parse_normal(fastObjData* data, const char* ptr)
623 {
624     unsigned int ii;
625     float        v;
626 
627 
628     for (ii = 0; ii < 3; ii++)
629     {
630         ptr = parse_float(ptr, &v);
631         array_push(data->mesh->normals, v);
632     }
633 
634     return ptr;
635 }
636 
637 
638 static
parse_face(fastObjData * data,const char * ptr)639 const char* parse_face(fastObjData* data, const char* ptr)
640 {
641     unsigned int count;
642     fastObjIndex vn;
643     int          v;
644     int          t;
645     int          n;
646 
647 
648     ptr = skip_whitespace(ptr);
649 
650     count = 0;
651     while (!is_newline(*ptr))
652     {
653         v = 0;
654         t = 0;
655         n = 0;
656 
657         ptr = parse_int(ptr, &v);
658         if (*ptr == '/')
659         {
660             ptr++;
661             if (*ptr != '/')
662                 ptr = parse_int(ptr, &t);
663 
664             if (*ptr == '/')
665             {
666                 ptr++;
667                 ptr = parse_int(ptr, &n);
668             }
669         }
670 
671         if (v < 0)
672             vn.p = (array_size(data->mesh->positions) / 3) - (unsigned int)(-v);
673         else
674             vn.p = (unsigned int)(v);
675 
676         if (t < 0)
677             vn.t = (array_size(data->mesh->texcoords) / 2) - (unsigned int)(-t);
678         else if (t > 0)
679             vn.t = (unsigned int)(t);
680         else
681             vn.t = 0;
682 
683         if (n < 0)
684             vn.n = (array_size(data->mesh->normals) / 3) - (unsigned int)(-n);
685         else if (n > 0)
686             vn.n = (unsigned int)(n);
687         else
688             vn.n = 0;
689 
690         array_push(data->mesh->indices, vn);
691         count++;
692 
693         ptr = skip_whitespace(ptr);
694     }
695 
696     array_push(data->mesh->face_vertices, count);
697     array_push(data->mesh->face_materials, data->material);
698 
699     data->group.face_count++;
700 
701     return ptr;
702 }
703 
704 
705 static
parse_group(fastObjData * data,const char * ptr)706 const char* parse_group(fastObjData* data, const char* ptr)
707 {
708     const char* s;
709     const char* e;
710 
711 
712     ptr = skip_whitespace(ptr);
713 
714     s = ptr;
715     while (!is_whitespace(*ptr) && !is_newline(*ptr))
716         ptr++;
717 
718     e = ptr;
719 
720     flush_output(data);
721     data->group.name = string_copy(s, e);
722 
723     return ptr;
724 }
725 
726 
727 static
map_default(void)728 fastObjTexture map_default(void)
729 {
730     fastObjTexture map;
731 
732     map.name = 0;
733     map.path = 0;
734 
735     return map;
736 }
737 
738 
739 static
mtl_default(void)740 fastObjMaterial mtl_default(void)
741 {
742     fastObjMaterial mtl;
743 
744     mtl.name = 0;
745 
746     mtl.Ka[0] = 0.0;
747     mtl.Ka[1] = 0.0;
748     mtl.Ka[2] = 0.0;
749     mtl.Kd[0] = 1.0;
750     mtl.Kd[1] = 1.0;
751     mtl.Kd[2] = 1.0;
752     mtl.Ks[0] = 0.0;
753     mtl.Ks[1] = 0.0;
754     mtl.Ks[2] = 0.0;
755     mtl.Ke[0] = 0.0;
756     mtl.Ke[1] = 0.0;
757     mtl.Ke[2] = 0.0;
758     mtl.Kt[0] = 0.0;
759     mtl.Kt[1] = 0.0;
760     mtl.Kt[2] = 0.0;
761     mtl.Ns    = 1.0;
762     mtl.Ni    = 1.0;
763     mtl.Tf[0] = 1.0;
764     mtl.Tf[1] = 1.0;
765     mtl.Tf[2] = 1.0;
766     mtl.d     = 1.0;
767     mtl.illum = 1;
768 
769     mtl.map_Ka   = map_default();
770     mtl.map_Kd   = map_default();
771     mtl.map_Ks   = map_default();
772     mtl.map_Ke   = map_default();
773     mtl.map_Kt   = map_default();
774     mtl.map_Ns   = map_default();
775     mtl.map_Ni   = map_default();
776     mtl.map_d    = map_default();
777     mtl.map_bump = map_default();
778 
779     return mtl;
780 }
781 
782 
783 static
parse_usemtl(fastObjData * data,const char * ptr)784 const char* parse_usemtl(fastObjData* data, const char* ptr)
785 {
786     const char*      s;
787     const char*      e;
788     unsigned int     idx;
789     fastObjMaterial* mtl;
790 
791 
792     ptr = skip_whitespace(ptr);
793 
794     /* Parse the material name */
795     s = ptr;
796     while (!is_whitespace(*ptr) && !is_newline(*ptr))
797         ptr++;
798 
799     e = ptr;
800 
801 
802     /* If there are no materials yet, add a dummy invalid material at index 0 */
803     if (array_empty(data->mesh->materials))
804         array_push(data->mesh->materials, mtl_default());
805 
806 
807     /* Find an existing material with the same name */
808     idx = 0;
809     while (idx < array_size(data->mesh->materials))
810     {
811         mtl = &data->mesh->materials[idx];
812         if (mtl->name && string_equal(mtl->name, s, e))
813             break;
814 
815         idx++;
816     }
817 
818     if (idx == array_size(data->mesh->materials))
819         idx = 0;
820 
821     data->material = idx;
822 
823     return ptr;
824 }
825 
826 
827 static
map_clean(fastObjTexture * map)828 void map_clean(fastObjTexture* map)
829 {
830     memory_dealloc(map->name);
831     memory_dealloc(map->path);
832 }
833 
834 
835 static
mtl_clean(fastObjMaterial * mtl)836 void mtl_clean(fastObjMaterial* mtl)
837 {
838     map_clean(&mtl->map_Ka);
839     map_clean(&mtl->map_Kd);
840     map_clean(&mtl->map_Ks);
841     map_clean(&mtl->map_Ke);
842     map_clean(&mtl->map_Kt);
843     map_clean(&mtl->map_Ns);
844     map_clean(&mtl->map_Ni);
845     map_clean(&mtl->map_d);
846     map_clean(&mtl->map_bump);
847 
848     memory_dealloc(mtl->name);
849 }
850 
851 
852 static
read_mtl_int(const char * p,int * v)853 const char* read_mtl_int(const char* p, int* v)
854 {
855     return parse_int(p, v);
856 }
857 
858 
859 static
read_mtl_single(const char * p,float * v)860 const char* read_mtl_single(const char* p, float* v)
861 {
862     return parse_float(p, v);
863 }
864 
865 
866 static
read_mtl_triple(const char * p,float v[3])867 const char* read_mtl_triple(const char* p, float v[3])
868 {
869     p = read_mtl_single(p, &v[0]);
870     p = read_mtl_single(p, &v[1]);
871     p = read_mtl_single(p, &v[2]);
872 
873     return p;
874 }
875 
876 
877 static
read_map(fastObjData * data,const char * ptr,fastObjTexture * map)878 const char* read_map(fastObjData* data, const char* ptr, fastObjTexture* map)
879 {
880     const char* s;
881     const char* e;
882     char*       name;
883     char*       path;
884 
885     ptr = skip_whitespace(ptr);
886 
887     /* Don't support options at present */
888     if (*ptr == '-')
889         return ptr;
890 
891 
892     /* Read name */
893     s = ptr;
894     while (!is_whitespace(*ptr) && !is_newline(*ptr))
895         ptr++;
896 
897     e = ptr;
898 
899     name = string_copy(s, e);
900 
901     path = string_concat(data->base, s, e);
902     string_fix_separators(path);
903 
904     map->name = name;
905     map->path = path;
906 
907     return e;
908 }
909 
910 
911 static
read_mtllib(fastObjData * data,void * file)912 int read_mtllib(fastObjData* data, void* file)
913 {
914     unsigned long   n;
915     const char*     s;
916     char*           contents;
917     size_t          l;
918     const char*     p;
919     const char*     e;
920     int             found_d;
921     fastObjMaterial mtl;
922 
923 
924     /* Read entire file */
925     n = file_size(file);
926 
927     contents = (char*)(memory_realloc(0, n + 1));
928     if (!contents)
929         return 0;
930 
931     l = file_read(file, contents, n);
932     contents[l] = '\n';
933 
934     mtl = mtl_default();
935 
936     found_d = 0;
937 
938     p = contents;
939     e = contents + l;
940     while (p < e)
941     {
942         p = skip_whitespace(p);
943 
944         switch (*p)
945         {
946         case 'n':
947             p++;
948             if (p[0] == 'e' &&
949                 p[1] == 'w' &&
950                 p[2] == 'm' &&
951                 p[3] == 't' &&
952                 p[4] == 'l' &&
953                 is_whitespace(p[5]))
954             {
955                 /* Push previous material (if there is one) */
956                 if (mtl.name)
957                 {
958                     array_push(data->mesh->materials, mtl);
959                     mtl = mtl_default();
960                 }
961 
962 
963                 /* Read name */
964                 p += 5;
965 
966                 while (is_whitespace(*p))
967                     p++;
968 
969                 s = p;
970                 while (!is_whitespace(*p) && !is_newline(*p))
971                     p++;
972 
973                 mtl.name = string_copy(s, p);
974             }
975             break;
976 
977         case 'K':
978             if (p[1] == 'a')
979                 p = read_mtl_triple(p + 2, mtl.Ka);
980             else if (p[1] == 'd')
981                 p = read_mtl_triple(p + 2, mtl.Kd);
982             else if (p[1] == 's')
983                 p = read_mtl_triple(p + 2, mtl.Ks);
984             else if (p[1] == 'e')
985                 p = read_mtl_triple(p + 2, mtl.Ke);
986             else if (p[1] == 't')
987                 p = read_mtl_triple(p + 2, mtl.Kt);
988             break;
989 
990         case 'N':
991             if (p[1] == 's')
992                 p = read_mtl_single(p + 2, &mtl.Ns);
993             else if (p[1] == 'i')
994                 p = read_mtl_single(p + 2, &mtl.Ni);
995             break;
996 
997         case 'T':
998             if (p[1] == 'r')
999             {
1000                 float Tr;
1001                 p = read_mtl_single(p + 2, &Tr);
1002                 if (!found_d)
1003                 {
1004                     /* Ignore Tr if we've already read d */
1005                     mtl.d = 1.0f - Tr;
1006                 }
1007             }
1008             else if (p[1] == 'f')
1009                 p = read_mtl_triple(p + 2, mtl.Tf);
1010             break;
1011 
1012         case 'd':
1013             if (is_whitespace(p[1]))
1014             {
1015                 p = read_mtl_single(p + 1, &mtl.d);
1016                 found_d = 1;
1017             }
1018             break;
1019 
1020         case 'i':
1021             p++;
1022             if (p[0] == 'l' &&
1023                 p[1] == 'l' &&
1024                 p[2] == 'u' &&
1025                 p[3] == 'm' &&
1026                 is_whitespace(p[4]))
1027             {
1028                 p = read_mtl_int(p + 4, &mtl.illum);
1029             }
1030             break;
1031 
1032         case 'm':
1033             p++;
1034             if (p[0] == 'a' &&
1035                 p[1] == 'p' &&
1036                 p[2] == '_')
1037             {
1038                 p += 3;
1039                 if (*p == 'K')
1040                 {
1041                     p++;
1042                     if (is_whitespace(p[1]))
1043                     {
1044                         if (*p == 'a')
1045                             p = read_map(data, p + 1, &mtl.map_Ka);
1046                         else if (*p == 'd')
1047                             p = read_map(data, p + 1, &mtl.map_Kd);
1048                         else if (*p == 's')
1049                             p = read_map(data, p + 1, &mtl.map_Ks);
1050                         else if (*p == 'e')
1051                             p = read_map(data, p + 1, &mtl.map_Ke);
1052                         else if (*p == 't')
1053                             p = read_map(data, p + 1, &mtl.map_Kt);
1054                     }
1055                 }
1056                 else if (*p == 'N')
1057                 {
1058                     p++;
1059                     if (is_whitespace(p[1]))
1060                     {
1061                         if (*p == 's')
1062                             p = read_map(data, p + 1, &mtl.map_Ns);
1063                         else if (*p == 'i')
1064                             p = read_map(data, p + 1, &mtl.map_Ni);
1065                     }
1066                 }
1067                 else if (*p == 'd')
1068                 {
1069                     p++;
1070                     if (is_whitespace(*p))
1071                         p = read_map(data, p, &mtl.map_d);
1072                 }
1073                 else if (p[0] == 'b' &&
1074                          p[1] == 'u' &&
1075                          p[2] == 'm' &&
1076                          p[3] == 'p' &&
1077                          is_whitespace(p[4]))
1078                 {
1079                     p = read_map(data, p + 4, &mtl.map_d);
1080                 }
1081             }
1082             break;
1083 
1084         case '#':
1085             break;
1086         }
1087 
1088         p = skip_line(p);
1089     }
1090 
1091     /* Push final material */
1092     if (mtl.name)
1093         array_push(data->mesh->materials, mtl);
1094 
1095     memory_dealloc(contents);
1096 
1097     return 1;
1098 }
1099 
1100 
1101 static
parse_mtllib(fastObjData * data,const char * ptr)1102 const char* parse_mtllib(fastObjData* data, const char* ptr)
1103 {
1104     const char* s;
1105     const char* e;
1106     char*       lib;
1107     void*       file;
1108 
1109 
1110     ptr = skip_whitespace(ptr);
1111 
1112     s = ptr;
1113     while (!is_whitespace(*ptr) && !is_newline(*ptr))
1114         ptr++;
1115 
1116     e = ptr;
1117 
1118     lib = string_concat(data->base, s, e);
1119     if (lib)
1120     {
1121         string_fix_separators(lib);
1122 
1123         file = file_open(lib);
1124         if (file)
1125         {
1126             read_mtllib(data, file);
1127             file_close(file);
1128         }
1129 
1130         memory_dealloc(lib);
1131     }
1132 
1133     return ptr;
1134 }
1135 
1136 
1137 static
parse_buffer(fastObjData * data,const char * ptr,const char * end)1138 void parse_buffer(fastObjData* data, const char* ptr, const char* end)
1139 {
1140     const char* p;
1141 
1142 
1143     p = ptr;
1144     while (p != end)
1145     {
1146         p = skip_whitespace(p);
1147 
1148         switch (*p)
1149         {
1150         case 'v':
1151             p++;
1152 
1153             switch (*p++)
1154             {
1155             case ' ':
1156             case '\t':
1157                 p = parse_vertex(data, p);
1158                 break;
1159 
1160             case 't':
1161                 p = parse_texcoord(data, p);
1162                 break;
1163 
1164             case 'n':
1165                 p = parse_normal(data, p);
1166                 break;
1167 
1168             default:
1169                 p--; /* roll p++ back in case *p was a newline */
1170             }
1171             break;
1172 
1173         case 'f':
1174             p++;
1175 
1176             switch (*p++)
1177             {
1178             case ' ':
1179             case '\t':
1180                 p = parse_face(data, p);
1181                 break;
1182 
1183             default:
1184                 p--; /* roll p++ back in case *p was a newline */
1185             }
1186             break;
1187 
1188         case 'g':
1189             p++;
1190 
1191             switch (*p++)
1192             {
1193             case ' ':
1194             case '\t':
1195                 p = parse_group(data, p);
1196                 break;
1197 
1198             default:
1199                 p--; /* roll p++ back in case *p was a newline */
1200             }
1201             break;
1202 
1203         case 'm':
1204             p++;
1205             if (p[0] == 't' &&
1206                 p[1] == 'l' &&
1207                 p[2] == 'l' &&
1208                 p[3] == 'i' &&
1209                 p[4] == 'b' &&
1210                 is_whitespace(p[5]))
1211                 p = parse_mtllib(data, p + 5);
1212             break;
1213 
1214         case 'u':
1215             p++;
1216             if (p[0] == 's' &&
1217                 p[1] == 'e' &&
1218                 p[2] == 'm' &&
1219                 p[3] == 't' &&
1220                 p[4] == 'l' &&
1221                 is_whitespace(p[5]))
1222                 p = parse_usemtl(data, p + 5);
1223             break;
1224 
1225         case '#':
1226             break;
1227         }
1228 
1229         p = skip_line(p);
1230 
1231         data->line++;
1232     }
1233 }
1234 
1235 
fast_obj_destroy(fastObjMesh * m)1236 void fast_obj_destroy(fastObjMesh* m)
1237 {
1238     unsigned int ii;
1239 
1240 
1241     for (ii = 0; ii < array_size(m->groups); ii++)
1242         group_clean(&m->groups[ii]);
1243 
1244     for (ii = 0; ii < array_size(m->materials); ii++)
1245         mtl_clean(&m->materials[ii]);
1246 
1247     array_clean(m->positions);
1248     array_clean(m->texcoords);
1249     array_clean(m->normals);
1250     array_clean(m->face_vertices);
1251     array_clean(m->face_materials);
1252     array_clean(m->indices);
1253     array_clean(m->groups);
1254     array_clean(m->materials);
1255 
1256     memory_dealloc(m);
1257 }
1258 
1259 
fast_obj_read(const char * path)1260 fastObjMesh* fast_obj_read(const char* path)
1261 {
1262     fastObjData  data;
1263     fastObjMesh* m;
1264     void*        file;
1265     char*        buffer;
1266     char*        start;
1267     char*        end;
1268     char*        last;
1269     unsigned int read;
1270     size_t       sep;
1271     unsigned int bytes;
1272 
1273 
1274     /* Open file */
1275     file = file_open(path);
1276     if (!file)
1277         return 0;
1278 
1279 
1280     /* Empty mesh */
1281     m = (fastObjMesh*)(memory_realloc(0, sizeof(fastObjMesh)));
1282     if (!m)
1283         return 0;
1284 
1285     m->positions      = 0;
1286     m->texcoords      = 0;
1287     m->normals        = 0;
1288     m->face_vertices  = 0;
1289     m->face_materials = 0;
1290     m->indices        = 0;
1291     m->materials      = 0;
1292     m->groups         = 0;
1293 
1294 
1295     /* Add dummy position/texcoord/normal */
1296     array_push(m->positions, 0.0f);
1297     array_push(m->positions, 0.0f);
1298     array_push(m->positions, 0.0f);
1299 
1300     array_push(m->texcoords, 0.0f);
1301     array_push(m->texcoords, 0.0f);
1302 
1303     array_push(m->normals, 0.0f);
1304     array_push(m->normals, 0.0f);
1305     array_push(m->normals, 1.0f);
1306 
1307 
1308     /* Data needed during parsing */
1309     data.mesh     = m;
1310     data.group    = group_default();
1311     data.material = 0;
1312     data.line     = 1;
1313     data.base     = 0;
1314 
1315 
1316     /* Find base path for materials/textures */
1317     if (string_find_last(path, FAST_OBJ_SEPARATOR, &sep))
1318         data.base = string_substr(path, 0, sep + 1);
1319 
1320 
1321     /* Create buffer for reading file */
1322     buffer = (char*)(memory_realloc(0, 2 * BUFFER_SIZE * sizeof(char)));
1323     if (!buffer)
1324         return 0;
1325 
1326     start = buffer;
1327     for (;;)
1328     {
1329         /* Read another buffer's worth from file */
1330         read = (unsigned int)(file_read(file, start, BUFFER_SIZE));
1331         if (read == 0 && start == buffer)
1332             break;
1333 
1334 
1335         /* Ensure buffer ends in a newline */
1336         if (read < BUFFER_SIZE)
1337         {
1338             if (read == 0 || start[read - 1] != '\n')
1339                 start[read++] = '\n';
1340         }
1341 
1342         end = start + read;
1343         if (end == buffer)
1344             break;
1345 
1346 
1347         /* Find last new line */
1348         last = end;
1349         while (last > buffer)
1350         {
1351             last--;
1352             if (*last == '\n')
1353                 break;
1354         }
1355 
1356 
1357         /* Check there actually is a new line */
1358         if (*last != '\n')
1359             break;
1360 
1361         last++;
1362 
1363 
1364         /* Process buffer */
1365         parse_buffer(&data, buffer, last);
1366 
1367 
1368         /* Copy overflow for next buffer */
1369         bytes = (unsigned int)(end - last);
1370         memcpy(buffer, last, bytes);
1371         start = buffer + bytes;
1372     }
1373 
1374 
1375     /* Flush final group */
1376     flush_output(&data);
1377     group_clean(&data.group);
1378 
1379     m->position_count = array_size(m->positions) / 3;
1380     m->texcoord_count = array_size(m->texcoords) / 2;
1381     m->normal_count   = array_size(m->normals) / 3;
1382     m->face_count     = array_size(m->face_vertices);
1383     m->material_count = array_size(m->materials);
1384     m->group_count    = array_size(m->groups);
1385 
1386 
1387     /* Clean up */
1388     memory_dealloc(buffer);
1389     memory_dealloc(data.base);
1390 
1391     file_close(file);
1392 
1393     return m;
1394 }
1395 
1396 #endif
1397 
1398