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