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