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