]> git.xonotic.org Git - xonotic/netradiant.git/blob - libs/picomodel/pm_obj.c
set eol-style
[xonotic/netradiant.git] / libs / picomodel / pm_obj.c
1 /* -----------------------------------------------------------------------------
2
3 PicoModel Library
4
5 Copyright (c) 2002, Randy Reddig & seaw0lf
6 All rights reserved.
7
8 Redistribution and use in source and binary forms, with or without modification,
9 are permitted provided that the following conditions are met:
10
11 Redistributions of source code must retain the above copyright notice, this list
12 of conditions and the following disclaimer.
13
14 Redistributions in binary form must reproduce the above copyright notice, this
15 list of conditions and the following disclaimer in the documentation and/or
16 other materials provided with the distribution.
17
18 Neither the names of the copyright holders nor the names of its contributors may
19 be used to endorse or promote products derived from this software without
20 specific prior written permission.
21
22 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
23 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
24 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
25 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
26 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
27 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
29 ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
31 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32
33 ----------------------------------------------------------------------------- */
34
35
36
37 /* marker */
38 #define PM_OBJ_C
39
40 /* dependencies */
41 #include "picointernal.h"
42
43 /* disable warnings */
44 #ifdef _WIN32
45 #pragma warning( disable:4100 )         /* unref param */
46 #endif
47
48 /* todo:
49  * - '_obj_load' code crashes in a weird way after
50  *   '_obj_mtl_load' for a few .mtl files
51  * - process 'mtllib' rather than using <model>.mtl
52  * - handle 'usemtl' statements
53  */
54 /* uncomment when debugging this module */
55 /* #define DEBUG_PM_OBJ */
56 /* #define DEBUG_PM_OBJ_EX */
57
58 /* this holds temporary vertex data read by parser */
59 typedef struct SObjVertexData
60 {
61         picoVec3_t      v;                      /* geometric vertices */
62         picoVec2_t      vt;                     /* texture vertices */
63         picoVec3_t      vn;                     /* vertex normals (optional) */
64 }
65 TObjVertexData;
66
67 /* _obj_canload:
68  *  validates a wavefront obj model file.
69  */
70 static int _obj_canload( PM_PARAMS_CANLOAD )
71 {
72         picoParser_t *p;
73
74         /* check data length */
75         if (bufSize < 30)
76                 return PICO_PMV_ERROR_SIZE;
77
78         /* first check file extension. we have to do this for objs */
79         /* cause there is no good way to identify the contents */
80         if (_pico_stristr(fileName,".obj") != NULL ||
81                 _pico_stristr(fileName,".wf" ) != NULL)
82         {
83                 return PICO_PMV_OK;
84         }
85         /* if the extension check failed we parse through the first */
86         /* few lines in file and look for common keywords often */
87         /* appearing at the beginning of wavefront objects */
88
89         /* alllocate a new pico parser */
90         p = _pico_new_parser( (picoByte_t *)buffer,bufSize );
91         if (p == NULL)
92                 return PICO_PMV_ERROR_MEMORY;
93
94         /* parse obj head line by line for type check */
95         while( 1 )
96         {
97                 /* get first token on line */
98                 if (_pico_parse_first( p ) == NULL)
99                         break;
100
101                 /* we only parse the first few lines, say 80 */
102                 if (p->curLine > 80)
103                         break;
104
105                 /* skip empty lines */
106                 if (p->token == NULL || !strlen( p->token ))
107                         continue;
108
109                 /* material library keywords are teh good */
110                 if (!_pico_stricmp(p->token,"usemtl") ||
111                         !_pico_stricmp(p->token,"mtllib") ||
112                         !_pico_stricmp(p->token,"g") ||
113                         !_pico_stricmp(p->token,"v"))   /* v,g bit fishy, but uh... */
114                 {
115                         /* free the pico parser thing */
116                         _pico_free_parser( p );
117
118                         /* seems to be a valid wavefront obj */
119                         return PICO_PMV_OK;
120                 }
121                 /* skip rest of line */
122                 _pico_parse_skip_rest( p );
123         }
124         /* free the pico parser thing */
125         _pico_free_parser( p );
126
127         /* doesn't really look like an obj to us */
128         return PICO_PMV_ERROR;
129 }
130
131 /* SizeObjVertexData:
132  *   This pretty piece of 'alloc ahead' code dynamically
133  *   allocates - and reallocates as soon as required -
134  *   my vertex data array in even steps.
135  */
136 #define SIZE_OBJ_STEP  4096
137
138 static TObjVertexData *SizeObjVertexData(
139         TObjVertexData *vertexData, int reqEntries,
140         int *entries, int *allocated)
141 {
142         int newAllocated;
143
144         /* sanity checks */
145         if (reqEntries < 1)
146                 return NULL;
147         if (entries == NULL || allocated == NULL)
148                 return NULL; /* must have */
149
150         /* no need to grow yet */
151         if (vertexData && (reqEntries < *allocated))
152         {
153                 *entries = reqEntries;
154                 return vertexData;
155         }
156         /* given vertex data ptr not allocated yet */
157         if (vertexData == NULL)
158         {
159                 /* how many entries to allocate */
160                 newAllocated = (reqEntries > SIZE_OBJ_STEP) ?
161                                                 reqEntries : SIZE_OBJ_STEP;
162
163                 /* throw out an extended debug message */
164 #ifdef DEBUG_PM_OBJ_EX
165                 printf("SizeObjVertexData: allocate (%d entries)\n",
166                         newAllocated);
167 #endif
168                 /* first time allocation */
169                 vertexData = (TObjVertexData *)
170                         _pico_alloc( sizeof(TObjVertexData) * newAllocated );
171
172                 /* allocation failed */
173                 if (vertexData == NULL)
174                         return NULL;
175
176                 /* allocation succeeded */
177                 *allocated = newAllocated;
178                 *entries   = reqEntries;
179                 return vertexData;
180         }
181         /* given vertex data ptr needs to be resized */
182         if (reqEntries == *allocated)
183         {
184                 newAllocated = (*allocated + SIZE_OBJ_STEP);
185
186                 /* throw out an extended debug message */
187 #ifdef DEBUG_PM_OBJ_EX
188                 printf("SizeObjVertexData: reallocate (%d entries)\n",
189                         newAllocated);
190 #endif
191                 /* try to reallocate */
192                 vertexData = (TObjVertexData *)
193                         _pico_realloc( (void *)&vertexData,
194                                 sizeof(TObjVertexData) * (*allocated),
195                                 sizeof(TObjVertexData) * (newAllocated));
196
197                 /* reallocation failed */
198                 if (vertexData == NULL)
199                         return NULL;
200
201                 /* reallocation succeeded */
202                 *allocated = newAllocated;
203                 *entries   = reqEntries;
204                 return vertexData;
205         }
206         /* we're b0rked when we reach this */
207         return NULL;
208 }
209
210 static void FreeObjVertexData( TObjVertexData *vertexData )
211 {
212         if (vertexData != NULL)
213         {
214                 free( (TObjVertexData *)vertexData );
215         }
216 }
217
218 static int _obj_mtl_load( picoModel_t *model )
219 {
220         picoShader_t *curShader = NULL;
221         picoParser_t *p;
222         picoByte_t   *mtlBuffer;
223         int                       mtlBufSize;
224         char             *fileName;
225
226         /* sanity checks */
227         if( model == NULL || model->fileName == NULL )
228                 return 0;
229
230         /* skip if we have a zero length model file name */
231         if (!strlen( model->fileName ))
232                 return 0;
233
234         /* helper */
235         #define _obj_mtl_error_return \
236         { \
237                 _pico_free_parser( p ); \
238                 _pico_free_file( mtlBuffer ); \
239                 _pico_free( fileName ); \
240                 return 0; \
241         }
242         /* alloc copy of model file name */
243         fileName = _pico_clone_alloc( model->fileName,-1 );
244         if (fileName == NULL)
245                 return 0;
246
247         /* change extension of model file to .mtl */
248         _pico_setfext( fileName, "mtl" );
249
250         /* load .mtl file contents */
251         _pico_load_file( fileName,&mtlBuffer,&mtlBufSize );
252
253         /* check result */
254         if (mtlBufSize == 0) return 1;          /* file is empty: no error */
255         if (mtlBufSize  < 0) return 0;          /* load failed: error */
256
257         /* create a new pico parser */
258         p = _pico_new_parser( mtlBuffer, mtlBufSize );
259         if (p == NULL)
260                 _obj_mtl_error_return;
261         
262         /* doo teh .mtl parse */
263         while( 1 )
264         {
265                 /* get next token in material file */
266                 if (_pico_parse( p,1 ) == NULL)
267                         break;
268 #if 0
269
270                 /* skip empty lines */
271                 if (p->token == NULL || !strlen( p->token ))
272                         continue;
273
274                 /* skip comment lines */
275                 if (p->token[0] == '#')
276                 {
277                         _pico_parse_skip_rest( p );
278                         continue;
279                 }
280                 /* new material */
281                 if (!_pico_stricmp(p->token,"newmtl"))
282                 {
283                         picoShader_t *shader;
284                         char *name;
285
286                         /* get material name */
287                         name = _pico_parse( p,0 );
288
289                         /* validate material name */
290                         if (name == NULL || !strlen(name))
291                         {
292                                 _pico_printf( PICO_ERROR,"Missing material name in MTL, line %d.",p->curLine);
293                                 _obj_mtl_error_return;
294                         }
295                         /* create a new pico shader */
296                         shader = PicoNewShader( model );
297                         if (shader == NULL)
298                                 _obj_mtl_error_return;
299
300                         /* set shader name */
301                         PicoSetShaderName( shader,name );
302
303                         /* assign pointer to current shader */
304                         curShader = shader;
305                 }
306                 /* diffuse map name */
307                 else if (!_pico_stricmp(p->token,"map_kd"))
308                 {
309                         char *mapName;
310
311                         /* pointer to current shader must be valid */
312                         if (curShader == NULL)
313                                 _obj_mtl_error_return;
314
315                         /* get material's diffuse map name */
316                         mapName = _pico_parse( p,0 );
317
318                         /* validate map name */
319                         if (mapName == NULL || !strlen(mapName))
320                         {
321                                 _pico_printf( PICO_ERROR,"Missing material map name in MTL, line %d.",p->curLine);
322                                 _obj_mtl_error_return;
323                         }
324                         /* set shader map name */
325                         PicoSetShaderMapName( shader,mapName );
326                 }
327                 /* dissolve factor (pseudo transparency 0..1) */
328                 /* where 0 means 100% transparent and 1 means opaque */
329                 else if (!_pico_stricmp(p->token,"d"))
330                 {
331                         picoByte_t *diffuse;
332                         float value;
333
334
335                         /* get dissolve factor */
336                         if (!_pico_parse_float( p,&value ))
337                                 _obj_mtl_error_return;
338
339                         /* set shader transparency */
340                         PicoSetShaderTransparency( curShader,value );
341
342                         /* get shader's diffuse color */
343                         diffuse = PicoGetShaderDiffuseColor( curShader );
344
345                         /* set diffuse alpha to transparency */
346                         diffuse[ 3 ] = (picoByte_t)( value * 255.0 );
347
348                         /* set shader's new diffuse color */
349                         PicoSetShaderDiffuseColor( curShader,diffuse );
350                 }
351                 /* shininess (phong specular component) */
352                 else if (!_pico_stricmp(p->token,"ns"))
353                 {
354                         /* remark:
355                          * - well, this is some major obj spec fuckup once again. some
356                          *   apps store this in 0..1 range, others use 0..100 range,
357                          *   even others use 0..2048 range, and again others use the
358                          *   range 0..128, some even use 0..1000, 0..200, 400..700,
359                          *   honestly, what's up with the 3d app coders? happens when
360                          *   you smoke too much weed i guess. -sea
361                          */
362                         float value;
363
364                         /* pointer to current shader must be valid */
365                         if (curShader == NULL)
366                                 _obj_mtl_error_return;
367
368                         /* get totally screwed up shininess (a random value in fact ;) */
369                         if (!_pico_parse_float( p,&value ))
370                                 _obj_mtl_error_return;
371
372                         /* okay, there is no way to set this correctly, so we simply */
373                         /* try to guess a few ranges (most common ones i have seen) */
374
375                         /* assume 0..2048 range */
376                         if (value > 1000)
377                                 value = 128.0 * (value / 2048.0);
378                         /* assume 0..1000 range */
379                         else if (value > 200)
380                                 value = 128.0 * (value / 1000.0);
381                         /* assume 0..200 range */
382                         else if (value > 100)
383                                 value = 128.0 * (value / 200.0);
384                         /* assume 0..100 range */
385                         else if (value > 1)
386                                 value = 128.0 * (value / 100.0);
387                         /* assume 0..1 range */
388                         else {
389                                 value *= 128.0;
390                         }
391                         /* negative shininess is bad (yes, i have seen it...) */
392                         if (value < 0.0) value = 0.0;
393
394                         /* set the pico shininess value in range 0..127 */
395                         /* geez, .obj is such a mess... */
396                         PicoSetShaderShininess( curShader,value );
397                 }
398                 /* kol0r ambient (wut teh fuk does "ka" stand for?) */
399                 else if (!_pico_stricmp(p->token,"ka"))
400                 {
401                         picoColor_t color;
402                         picoVec3_t  v;
403
404                         /* pointer to current shader must be valid */
405                         if (curShader == NULL)
406                                 _obj_mtl_error_return;
407
408                         /* get color vector */
409                         if (!_pico_parse_vec( p,v ))
410                                 _obj_mtl_error_return;
411
412                         /* scale to byte range */
413                         color[ 0 ] = (picoByte_t)( v[ 0 ] * 255 );
414                         color[ 1 ] = (picoByte_t)( v[ 1 ] * 255 );
415                         color[ 2 ] = (picoByte_t)( v[ 2 ] * 255 );
416                         color[ 3 ] = (picoByte_t)( 255 );
417
418                         /* set ambient color */
419                         PicoSetShaderAmbientColor( curShader,color );
420                 }
421                 /* kol0r diffuse */
422                 else if (!_pico_stricmp(p->token,"kd"))
423                 {
424                         picoColor_t color;
425                         picoVec3_t  v;
426
427                         /* pointer to current shader must be valid */
428                         if (curShader == NULL)
429                                 _obj_mtl_error_return;
430
431                         /* get color vector */
432                         if (!_pico_parse_vec( p,v ))
433                                 _obj_mtl_error_return;
434
435                         /* scale to byte range */
436                         color[ 0 ] = (picoByte_t)( v[ 0 ] * 255 );
437                         color[ 1 ] = (picoByte_t)( v[ 1 ] * 255 );
438                         color[ 2 ] = (picoByte_t)( v[ 2 ] * 255 );
439                         color[ 3 ] = (picoByte_t)( 255 );
440
441                         /* set diffuse color */
442                         PicoSetShaderDiffuseColor( curShader,color );
443                 }
444                 /* kol0r specular */
445                 else if (!_pico_stricmp(p->token,"ks"))
446                 {
447                         picoColor_t color;
448                         picoVec3_t  v;
449
450                         /* pointer to current shader must be valid */
451                         if (curShader == NULL)
452                                 _obj_mtl_error_return;
453
454                         /* get color vector */
455                         if (!_pico_parse_vec( p,v ))
456                                 _obj_mtl_error_return;
457
458                         /* scale to byte range */
459                         color[ 0 ] = (picoByte_t)( v[ 0 ] * 255 );
460                         color[ 1 ] = (picoByte_t)( v[ 1 ] * 255 );
461                         color[ 2 ] = (picoByte_t)( v[ 2 ] * 255 );
462                         color[ 3 ] = (picoByte_t)( 255 );
463
464                         /* set specular color */
465                         PicoSetShaderSpecularColor( curShader,color );
466                 }
467 #endif
468                 /* skip rest of line */
469                 _pico_parse_skip_rest( p );
470         }
471
472         /* free parser, file buffer, and file name */
473         _pico_free_parser( p );
474         _pico_free_file( mtlBuffer );
475         _pico_free( fileName );
476
477         /* return with success */
478         return 1;
479 }
480
481 /* _obj_load:
482  *  loads a wavefront obj model file.
483 */
484 static picoModel_t *_obj_load( PM_PARAMS_LOAD )
485 {
486         TObjVertexData *vertexData  = NULL;
487         picoModel_t    *model;
488         picoSurface_t  *curSurface  = NULL;
489         picoParser_t   *p;
490         int                             allocated;
491         int                         entries;
492         int                             numVerts        = 0;
493         int                             numNormals      = 0;
494         int                             numUVs          = 0;
495         int                             curVertex       = 0;
496         int                             curFace         = 0;
497
498         /* helper */
499         #define _obj_error_return(m) \
500         { \
501                 _pico_printf( PICO_ERROR,"%s in OBJ, line %d.",m,p->curLine); \
502                 _pico_free_parser( p ); \
503                 FreeObjVertexData( vertexData ); \
504                 PicoFreeModel( model ); \
505                 return NULL; \
506         }
507         /* alllocate a new pico parser */
508         p = _pico_new_parser( (picoByte_t *)buffer,bufSize );
509         if (p == NULL) return NULL;
510
511         /* create a new pico model */
512         model = PicoNewModel();
513         if (model == NULL)
514         {
515                 _pico_free_parser( p );
516                 return NULL;
517         }
518         /* do model setup */
519         PicoSetModelFrameNum( model,frameNum );
520         PicoSetModelName( model,fileName );
521         PicoSetModelFileName( model,fileName );
522
523         /* try loading the materials; we don't handle the result */
524 #if 0
525         _obj_mtl_load( model );
526 #endif
527
528         /* parse obj line by line */
529         while( 1 )
530         {
531                 /* get first token on line */
532                 if (_pico_parse_first( p ) == NULL)
533                         break;
534
535                 /* skip empty lines */
536                 if (p->token == NULL || !strlen( p->token ))
537                         continue;
538
539                 /* skip comment lines */
540                 if (p->token[0] == '#')
541                 {
542                         _pico_parse_skip_rest( p );
543                         continue;
544                 }
545                 /* vertex */
546                 if (!_pico_stricmp(p->token,"v"))
547                 {
548                         TObjVertexData *data;
549                         picoVec3_t v;
550
551                         vertexData = SizeObjVertexData( vertexData,numVerts+1,&entries,&allocated );
552                         if (vertexData == NULL)
553                                 _obj_error_return("Realloc of vertex data failed (1)");
554
555                         data = &vertexData[ numVerts++ ];
556
557                         /* get and copy vertex */
558                         if (!_pico_parse_vec( p,v ))
559                                 _obj_error_return("Vertex parse error");
560
561                         _pico_copy_vec( v,data->v );
562
563 #ifdef DEBUG_PM_OBJ_EX
564                         printf("Vertex: x: %f y: %f z: %f\n",v[0],v[1],v[2]);
565 #endif
566                 }
567                 /* uv coord */
568                 else if (!_pico_stricmp(p->token,"vt"))
569                 {
570                         TObjVertexData *data;
571                         picoVec2_t coord;
572
573                         vertexData = SizeObjVertexData( vertexData,numUVs+1,&entries,&allocated );
574                         if (vertexData == NULL)
575                                 _obj_error_return("Realloc of vertex data failed (2)");
576
577                         data = &vertexData[ numUVs++ ];
578
579                         /* get and copy tex coord */
580                         if (!_pico_parse_vec2( p,coord ))
581                                 _obj_error_return("UV coord parse error");
582
583                         _pico_copy_vec2( coord,data->vt );
584
585 #ifdef DEBUG_PM_OBJ_EX
586                         printf("TexCoord: u: %f v: %f\n",coord[0],coord[1]);
587 #endif
588                 }
589                 /* vertex normal */
590                 else if (!_pico_stricmp(p->token,"vn"))
591                 {
592                         TObjVertexData *data;
593                         picoVec3_t n;
594
595                         vertexData = SizeObjVertexData( vertexData,numNormals+1,&entries,&allocated );
596                         if (vertexData == NULL)
597                                 _obj_error_return("Realloc of vertex data failed (3)");
598
599                         data = &vertexData[ numNormals++ ];
600
601                         /* get and copy vertex normal */
602                         if (!_pico_parse_vec( p,n ))
603                                 _obj_error_return("Vertex normal parse error");
604
605                         _pico_copy_vec( n,data->vn );
606
607 #ifdef DEBUG_PM_OBJ_EX
608                         printf("Normal: x: %f y: %f z: %f\n",n[0],n[1],n[2]);
609 #endif
610                 }
611                 /* new group (for us this means a new surface) */
612                 else if (!_pico_stricmp(p->token,"g"))
613                 {
614                         picoSurface_t *newSurface;
615                         char *groupName;
616
617                         /* get first group name (ignore 2nd,3rd,etc.) */
618                         groupName = _pico_parse( p,0 );
619                         if (groupName == NULL || !strlen(groupName))
620                         {
621                                 /* some obj exporters feel like they don't need to */
622                                 /* supply a group name. so we gotta handle it here */
623 #if 1
624                                 strcpy( p->token,"default" );
625                                 groupName = p->token;
626 #else
627                                 _obj_error_return("Invalid or missing group name");
628 #endif
629                         }
630                         /* allocate a pico surface */
631                         newSurface = PicoNewSurface( model );
632                         if (newSurface == NULL)
633                                 _obj_error_return("Error allocating surface");
634
635                         /* reset face index for surface */
636                         curFace = 0;
637
638                         /* set ptr to current surface */
639                         curSurface = newSurface;
640
641                         /* we use triangle meshes */
642                         PicoSetSurfaceType( newSurface,PICO_TRIANGLES );
643
644                         /* set surface name */
645                         PicoSetSurfaceName( newSurface,groupName );
646
647 #ifdef DEBUG_PM_OBJ_EX
648                         printf("Group: '%s'\n",groupName);
649 #endif
650                 }
651                 /* face (oh jesus, hopefully this will do the job right ;) */
652                 else if (!_pico_stricmp(p->token,"f"))
653                 {
654                         /* okay, this is a mess. some 3d apps seem to try being unique, */
655                         /* hello cinema4d & 3d exploration, feel good today?, and save */
656                         /* this crap in tons of different formats. gah, those screwed */
657                         /* coders. tho the wavefront obj standard defines exactly two */
658                         /* ways of storing face information. so, i really won't support */
659                         /* such stupid extravaganza here! */
660
661                         picoVec3_t verts  [ 4 ];
662                         picoVec3_t normals[ 4 ];
663                         picoVec2_t coords [ 4 ];
664
665                         int iv [ 4 ], has_v;
666                         int ivt[ 4 ], has_vt = 0;
667                         int ivn[ 4 ], has_vn = 0;
668                         int have_quad = 0;
669                         int slashcount;
670                         int doubleslash;
671                         int i;
672
673                         /* group defs *must* come before faces */
674                         if (curSurface == NULL)
675                                 _obj_error_return("No group defined for faces");
676
677 #ifdef DEBUG_PM_OBJ_EX
678                         printf("Face: ");
679 #endif
680                         /* read vertex/uv/normal indices for the first three face */
681                         /* vertices (cause we only support triangles) into 'i*[]' */
682                         /* store the actual vertex/uv/normal data in three arrays */
683                         /* called 'verts','coords' and 'normals'. */
684                         for (i=0; i<4; i++)
685                         {
686                                 char *str;
687
688                                 /* get next vertex index string (different */
689                                 /* formats are handled below) */
690                                 str = _pico_parse( p,0 );
691                                 if (str == NULL)
692                                 {
693                                         /* just break for quads */
694                                         if (i == 3) break;
695
696                                         /* error otherwise */
697                                         _obj_error_return("Face parse error");
698                                 }
699                                 /* if this is the fourth index string we're */
700                                 /* parsing we assume that we have a quad */
701                                 if (i == 3)
702                                         have_quad = 1;
703
704                                 /* get slash count once */
705                                 if (i == 0)
706                                 {
707                                         slashcount  = _pico_strchcount( str,'/' );
708                                         doubleslash =  strstr(str,"//") != NULL;
709                                 }
710                                 /* handle format 'v//vn' */
711                                 if (doubleslash && (slashcount == 2))
712                                 {
713                                         has_v = has_vn = 1;
714                                         sscanf( str,"%d//%d",&iv[ i ],&ivn[ i ] );
715                                 }
716                                 /* handle format 'v/vt/vn' */
717                                 else if (!doubleslash && (slashcount == 2))
718                                 {
719                                         has_v = has_vt = has_vn = 1;
720                                         sscanf( str,"%d/%d/%d",&iv[ i ],&ivt[ i ],&ivn[ i ] );
721                                 }
722                                 /* handle format 'v/vt' (non-standard fuckage) */
723                                 else if (!doubleslash && (slashcount == 1))
724                                 {
725                                         has_v = has_vt = 1;
726                                         sscanf( str,"%d/%d",&iv[ i ],&ivt[ i ] );
727                                 }
728                                 /* else assume face format 'v' */
729                                 /* (must have been invented by some bored granny) */
730                                 else {
731                                         /* get single vertex index */
732                                         has_v = 1;
733                                         iv[ i ] = atoi( str );
734
735                                         /* either invalid face format or out of range */
736                                         if (iv[ i ] == 0)
737                                                 _obj_error_return("Invalid face format");
738                                 }
739                                 /* fix useless back references */
740                                 /* todo: check if this works as it is supposed to */
741
742                                 /* assign new indices */
743                                 if (iv [ i ] < 0) iv [ i ] = (numVerts   - iv [ i ]);
744                                 if (ivt[ i ] < 0) ivt[ i ] = (numUVs     - ivt[ i ]);
745                                 if (ivn[ i ] < 0) ivn[ i ] = (numNormals - ivn[ i ]);
746
747                                 /* validate indices */
748                                 /* - commented out. index range checks will trigger
749                                 if (iv [ i ] < 1) iv [ i ] = 1;
750                                 if (ivt[ i ] < 1) ivt[ i ] = 1;
751                                 if (ivn[ i ] < 1) ivn[ i ] = 1;
752                                 */
753                                 /* set vertex origin */
754                                 if (has_v)
755                                 {
756                                         /* check vertex index range */
757                                         if (iv[ i ] < 1 || iv[ i ] > numVerts)
758                                                 _obj_error_return("Vertex index out of range");
759
760                                         /* get vertex data */
761                                         verts[ i ][ 0 ] = vertexData[ iv[ i ] - 1 ].v[ 0 ];
762                                         verts[ i ][ 1 ] = vertexData[ iv[ i ] - 1 ].v[ 1 ];
763                                         verts[ i ][ 2 ] = vertexData[ iv[ i ] - 1 ].v[ 2 ];
764                                 }
765                                 /* set vertex normal */
766                                 if (has_vn)
767                                 {
768                                         /* check normal index range */
769                                         if (ivn[ i ] < 1 || ivn[ i ] > numNormals)
770                                                 _obj_error_return("Normal index out of range");
771
772                                         /* get normal data */
773                                         normals[ i ][ 0 ] = vertexData[ ivn[ i ] - 1 ].vn[ 0 ];
774                                         normals[ i ][ 1 ] = vertexData[ ivn[ i ] - 1 ].vn[ 1 ];
775                                         normals[ i ][ 2 ] = vertexData[ ivn[ i ] - 1 ].vn[ 2 ];
776                                 }
777                                 /* set texture coordinate */
778                                 if (has_vt)
779                                 {
780                                         /* check uv index range */
781                                         if (ivt[ i ] < 1 || ivt[ i ] > numUVs)
782                                                 _obj_error_return("UV coord index out of range");
783
784                                         /* get uv coord data */
785                                         coords[ i ][ 0 ] = vertexData[ ivt[ i ] - 1 ].vt[ 0 ];
786                                         coords[ i ][ 1 ] = vertexData[ ivt[ i ] - 1 ].vt[ 1 ];
787                                         coords[ i ][ 1 ] = -coords[ i ][ 1 ];
788                                 }
789 #ifdef DEBUG_PM_OBJ_EX
790                                 printf("(%4d",iv[ i ]);
791                                 if (has_vt) printf(" %4d",ivt[ i ]);
792                                 if (has_vn) printf(" %4d",ivn[ i ]);
793                                 printf(") ");
794 #endif
795                         }
796 #ifdef DEBUG_PM_OBJ_EX
797                         printf("\n");
798 #endif
799                         /* now that we have extracted all the indices and have */
800                         /* read the actual data we need to assign all the crap */
801                         /* to our current pico surface */
802                         if (has_v)
803                         {
804                                 int max = 3;
805                                 if (have_quad) max = 4;
806
807                                 /* assign all surface information */
808                                 for (i=0; i<max; i++)
809                                 {
810                                         /*if( has_v  )*/ PicoSetSurfaceXYZ       ( curSurface,  (curVertex + i), verts  [ i ] );
811                                         /*if( has_vt )*/ PicoSetSurfaceST        ( curSurface,0,(curVertex + i), coords [ i ] );
812                                         /*if( has_vn )*/ PicoSetSurfaceNormal( curSurface,  (curVertex + i), normals[ i ] );
813                                 }
814                                 /* add our triangle (A B C) */
815                                 PicoSetSurfaceIndex( curSurface,(curFace * 3 + 2),(picoIndex_t)( curVertex + 0 ) );
816                                 PicoSetSurfaceIndex( curSurface,(curFace * 3 + 1),(picoIndex_t)( curVertex + 1 ) );
817                                 PicoSetSurfaceIndex( curSurface,(curFace * 3 + 0),(picoIndex_t)( curVertex + 2 ) );
818                                 curFace++;
819
820                                 /* if we don't have a simple triangle, but a quad... */
821                                 if (have_quad)
822                                 {
823                                         /* we have to add another triangle (2nd half of quad which is A C D) */
824                                         PicoSetSurfaceIndex( curSurface,(curFace * 3 + 2),(picoIndex_t)( curVertex + 0 ) );
825                                         PicoSetSurfaceIndex( curSurface,(curFace * 3 + 1),(picoIndex_t)( curVertex + 2 ) );
826                                         PicoSetSurfaceIndex( curSurface,(curFace * 3 + 0),(picoIndex_t)( curVertex + 3 ) );
827                                         curFace++;
828                                 }
829                                 /* increase vertex count */
830                                 curVertex += max;
831                         }
832                 }
833                 /* skip unparsed rest of line and continue */
834                 _pico_parse_skip_rest( p );
835         }
836         /* free memory used by temporary vertexdata */
837         FreeObjVertexData( vertexData );
838
839         /* return allocated pico model */
840         return model;
841 //      return NULL;
842 }
843
844 /* pico file format module definition */
845 const picoModule_t picoModuleOBJ =
846 {
847         "0.6-b",                                        /* module version string */
848         "Wavefront ASCII",                      /* module display name */
849         "seaw0lf",                                      /* author's name */
850         "2002 seaw0lf",                         /* module copyright */
851         {
852                 "obj",NULL,NULL,NULL    /* default extensions to use */
853         },
854         _obj_canload,                           /* validation routine */
855         _obj_load,                                      /* load routine */
856          NULL,                                          /* save validation routine */
857          NULL                                           /* save routine */
858 };