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