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