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