]> de.git.xonotic.org Git - xonotic/netradiant.git/blob - libs/picomodel/pm_obj.c
merge branch work back into trunk
[xonotic/netradiant.git] / libs / picomodel / pm_obj.c
1 /* -----------------------------------------------------------------------------
2
3 PicoModel Library
4
5 Copyright (c) 2002, Randy Reddig & seaw0lf
6 All rights reserved.
7
8 Redistribution and use in source and binary forms, with or without modification,
9 are permitted provided that the following conditions are met:
10
11 Redistributions of source code must retain the above copyright notice, this list
12 of conditions and the following disclaimer.
13
14 Redistributions in binary form must reproduce the above copyright notice, this
15 list of conditions and the following disclaimer in the documentation and/or
16 other materials provided with the distribution.
17
18 Neither the names of the copyright holders nor the names of its contributors may
19 be used to endorse or promote products derived from this software without
20 specific prior written permission.
21
22 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
23 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
24 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
25 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
26 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
27 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
29 ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
31 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32
33 ----------------------------------------------------------------------------- */
34
35
36
37 /* marker */
38 #define PM_OBJ_C
39
40 /* dependencies */
41 #include "picointernal.h"
42
43 /* disable warnings */
44 #ifdef _WIN32
45 #pragma warning( disable:4100 )         /* unref param */
46 #endif
47
48 /* todo:
49  * - '_obj_load' code crashes in a weird way after
50  *   '_obj_mtl_load' for a few .mtl files
51  * - process 'mtllib' rather than using <model>.mtl
52  * - handle 'usemtl' statements
53  */
54 /* uncomment when debugging this module */
55 /* #define DEBUG_PM_OBJ */
56 /* #define DEBUG_PM_OBJ_EX */
57
58 /* this holds temporary vertex data read by parser */
59 typedef struct SObjVertexData
60 {
61         picoVec3_t      v;                      /* geometric vertices */
62         picoVec2_t      vt;                     /* texture vertices */
63         picoVec3_t      vn;                     /* vertex normals (optional) */
64 }
65 TObjVertexData;
66
67 /* _obj_canload:
68  *  validates a wavefront obj model file.
69  */
70 static int _obj_canload( PM_PARAMS_CANLOAD )
71 {
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 #if 0
219 static int _obj_mtl_load( picoModel_t *model )
220 {
221         //picoShader_t *curShader = NULL;
222         picoParser_t *p;
223         picoByte_t   *mtlBuffer;
224         int                       mtlBufSize;
225         char             *fileName;
226
227         /* sanity checks */
228         if( model == NULL || model->fileName == NULL )
229                 return 0;
230
231         /* skip if we have a zero length model file name */
232         if (!strlen( model->fileName ))
233                 return 0;
234
235         /* helper */
236         #define _obj_mtl_error_return \
237         { \
238                 _pico_free_parser( p ); \
239                 _pico_free_file( mtlBuffer ); \
240                 _pico_free( fileName ); \
241                 return 0; \
242         }
243         /* alloc copy of model file name */
244         fileName = _pico_clone_alloc( model->fileName,-1 );
245         if (fileName == NULL)
246                 return 0;
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) return 1;          /* file is empty: no error */
256         if (mtlBufSize  < 0) return 0;          /* load failed: error */
257
258         /* create a new pico parser */
259         p = _pico_new_parser( mtlBuffer, mtlBufSize );
260         if (p == NULL)
261                 _obj_mtl_error_return;
262
263         /* doo teh .mtl parse */
264         while( 1 )
265         {
266                 /* get next token in material file */
267                 if (_pico_parse( p,1 ) == NULL)
268                         break;
269 #if 0
270
271                 /* skip empty lines */
272                 if (p->token == NULL || !strlen( p->token ))
273                         continue;
274
275                 /* skip comment lines */
276                 if (p->token[0] == '#')
277                 {
278                         _pico_parse_skip_rest( p );
279                         continue;
280                 }
281                 /* new material */
282                 if (!_pico_stricmp(p->token,"newmtl"))
283                 {
284                         picoShader_t *shader;
285                         char *name;
286
287                         /* get material name */
288                         name = _pico_parse( p,0 );
289
290                         /* validate material name */
291                         if (name == NULL || !strlen(name))
292                         {
293                                 _pico_printf( PICO_ERROR,"Missing material name in MTL, line %d.",p->curLine);
294                                 _obj_mtl_error_return;
295                         }
296                         /* create a new pico shader */
297                         shader = PicoNewShader( model );
298                         if (shader == NULL)
299                                 _obj_mtl_error_return;
300
301                         /* set shader name */
302                         PicoSetShaderName( shader,name );
303
304                         /* assign pointer to current shader */
305                         curShader = shader;
306                 }
307                 /* diffuse map name */
308                 else if (!_pico_stricmp(p->token,"map_kd"))
309                 {
310                         char *mapName;
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                         /* set shader map name */
326                         PicoSetShaderMapName( shader,mapName );
327                 }
328                 /* dissolve factor (pseudo transparency 0..1) */
329                 /* where 0 means 100% transparent and 1 means opaque */
330                 else if (!_pico_stricmp(p->token,"d"))
331                 {
332                         picoByte_t *diffuse;
333                         float value;
334
335
336                         /* get dissolve factor */
337                         if (!_pico_parse_float( p,&value ))
338                                 _obj_mtl_error_return;
339
340                         /* set shader transparency */
341                         PicoSetShaderTransparency( curShader,value );
342
343                         /* get shader's diffuse color */
344                         diffuse = PicoGetShaderDiffuseColor( curShader );
345
346                         /* set diffuse alpha to transparency */
347                         diffuse[ 3 ] = (picoByte_t)( value * 255.0 );
348
349                         /* set shader's new diffuse color */
350                         PicoSetShaderDiffuseColor( curShader,diffuse );
351                 }
352                 /* shininess (phong specular component) */
353                 else if (!_pico_stricmp(p->token,"ns"))
354                 {
355                         /* remark:
356                          * - well, this is some major obj spec fuckup once again. some
357                          *   apps store this in 0..1 range, others use 0..100 range,
358                          *   even others use 0..2048 range, and again others use the
359                          *   range 0..128, some even use 0..1000, 0..200, 400..700,
360                          *   honestly, what's up with the 3d app coders? happens when
361                          *   you smoke too much weed i guess. -sea
362                          */
363                         float value;
364
365                         /* pointer to current shader must be valid */
366                         if (curShader == NULL)
367                                 _obj_mtl_error_return;
368
369                         /* get totally screwed up shininess (a random value in fact ;) */
370                         if (!_pico_parse_float( p,&value ))
371                                 _obj_mtl_error_return;
372
373                         /* okay, there is no way to set this correctly, so we simply */
374                         /* try to guess a few ranges (most common ones i have seen) */
375
376                         /* assume 0..2048 range */
377                         if (value > 1000)
378                                 value = 128.0 * (value / 2048.0);
379                         /* assume 0..1000 range */
380                         else if (value > 200)
381                                 value = 128.0 * (value / 1000.0);
382                         /* assume 0..200 range */
383                         else if (value > 100)
384                                 value = 128.0 * (value / 200.0);
385                         /* assume 0..100 range */
386                         else if (value > 1)
387                                 value = 128.0 * (value / 100.0);
388                         /* assume 0..1 range */
389                         else {
390                                 value *= 128.0;
391                         }
392                         /* negative shininess is bad (yes, i have seen it...) */
393                         if (value < 0.0) value = 0.0;
394
395                         /* set the pico shininess value in range 0..127 */
396                         /* geez, .obj is such a mess... */
397                         PicoSetShaderShininess( curShader,value );
398                 }
399                 /* kol0r ambient (wut teh fuk does "ka" stand for?) */
400                 else if (!_pico_stricmp(p->token,"ka"))
401                 {
402                         picoColor_t color;
403                         picoVec3_t  v;
404
405                         /* pointer to current shader must be valid */
406                         if (curShader == NULL)
407                                 _obj_mtl_error_return;
408
409                         /* get color vector */
410                         if (!_pico_parse_vec( p,v ))
411                                 _obj_mtl_error_return;
412
413                         /* scale to byte range */
414                         color[ 0 ] = (picoByte_t)( v[ 0 ] * 255 );
415                         color[ 1 ] = (picoByte_t)( v[ 1 ] * 255 );
416                         color[ 2 ] = (picoByte_t)( v[ 2 ] * 255 );
417                         color[ 3 ] = (picoByte_t)( 255 );
418
419                         /* set ambient color */
420                         PicoSetShaderAmbientColor( curShader,color );
421                 }
422                 /* kol0r diffuse */
423                 else if (!_pico_stricmp(p->token,"kd"))
424                 {
425                         picoColor_t color;
426                         picoVec3_t  v;
427
428                         /* pointer to current shader must be valid */
429                         if (curShader == NULL)
430                                 _obj_mtl_error_return;
431
432                         /* get color vector */
433                         if (!_pico_parse_vec( p,v ))
434                                 _obj_mtl_error_return;
435
436                         /* scale to byte range */
437                         color[ 0 ] = (picoByte_t)( v[ 0 ] * 255 );
438                         color[ 1 ] = (picoByte_t)( v[ 1 ] * 255 );
439                         color[ 2 ] = (picoByte_t)( v[ 2 ] * 255 );
440                         color[ 3 ] = (picoByte_t)( 255 );
441
442                         /* set diffuse color */
443                         PicoSetShaderDiffuseColor( curShader,color );
444                 }
445                 /* kol0r specular */
446                 else if (!_pico_stricmp(p->token,"ks"))
447                 {
448                         picoColor_t color;
449                         picoVec3_t  v;
450
451                         /* pointer to current shader must be valid */
452                         if (curShader == NULL)
453                                 _obj_mtl_error_return;
454
455                         /* get color vector */
456                         if (!_pico_parse_vec( p,v ))
457                                 _obj_mtl_error_return;
458
459                         /* scale to byte range */
460                         color[ 0 ] = (picoByte_t)( v[ 0 ] * 255 );
461                         color[ 1 ] = (picoByte_t)( v[ 1 ] * 255 );
462                         color[ 2 ] = (picoByte_t)( v[ 2 ] * 255 );
463                         color[ 3 ] = (picoByte_t)( 255 );
464
465                         /* set specular color */
466                         PicoSetShaderSpecularColor( curShader,color );
467                 }
468 #endif
469                 /* skip rest of line */
470                 _pico_parse_skip_rest( p );
471         }
472
473         /* free parser, file buffer, and file name */
474         _pico_free_parser( p );
475         _pico_free_file( mtlBuffer );
476         _pico_free( fileName );
477
478         /* return with success */
479         return 1;
480 }
481 #endif
482
483 /* _obj_load:
484  *  loads a wavefront obj model file.
485 */
486 static picoModel_t *_obj_load( PM_PARAMS_LOAD )
487 {
488         TObjVertexData *vertexData  = NULL;
489         picoModel_t    *model;
490         picoSurface_t  *curSurface  = NULL;
491         picoParser_t   *p;
492         int                             allocated;
493         int                         entries;
494         int                             numVerts        = 0;
495         int                             numNormals      = 0;
496         int                             numUVs          = 0;
497         int                             curVertex       = 0;
498         int                             curFace         = 0;
499
500         /* helper */
501         #define _obj_error_return(m) \
502         { \
503                 _pico_printf( PICO_ERROR,"%s in OBJ, line %d.",m,p->curLine); \
504                 _pico_free_parser( p ); \
505                 FreeObjVertexData( vertexData ); \
506                 PicoFreeModel( model ); \
507                 return NULL; \
508         }
509         /* alllocate a new pico parser */
510         p = _pico_new_parser( (picoByte_t *)buffer,bufSize );
511         if (p == NULL) return NULL;
512
513         /* create a new pico model */
514         model = PicoNewModel();
515         if (model == NULL)
516         {
517                 _pico_free_parser( p );
518                 return NULL;
519         }
520         /* do model setup */
521         PicoSetModelFrameNum( model,frameNum );
522         PicoSetModelName( model,fileName );
523         PicoSetModelFileName( model,fileName );
524
525         /* try loading the materials; we don't handle the result */
526 #if 0
527         _obj_mtl_load( model );
528 #endif
529
530         /* parse obj line by line */
531         while( 1 )
532         {
533                 /* get first token on line */
534                 if (_pico_parse_first( p ) == NULL)
535                         break;
536
537                 /* skip empty lines */
538                 if (p->token == NULL || !strlen( p->token ))
539                         continue;
540
541                 /* skip comment lines */
542                 if (p->token[0] == '#')
543                 {
544                         _pico_parse_skip_rest( p );
545                         continue;
546                 }
547                 /* vertex */
548                 if (!_pico_stricmp(p->token,"v"))
549                 {
550                         TObjVertexData *data;
551                         picoVec3_t v;
552
553                         vertexData = SizeObjVertexData( vertexData,numVerts+1,&entries,&allocated );
554                         if (vertexData == NULL)
555                                 _obj_error_return("Realloc of vertex data failed (1)");
556
557                         data = &vertexData[ numVerts++ ];
558
559                         /* get and copy vertex */
560                         if (!_pico_parse_vec( p,v ))
561                                 _obj_error_return("Vertex parse error");
562
563                         _pico_copy_vec( v,data->v );
564
565 #ifdef DEBUG_PM_OBJ_EX
566                         printf("Vertex: x: %f y: %f z: %f\n",v[0],v[1],v[2]);
567 #endif
568                 }
569                 /* uv coord */
570                 else if (!_pico_stricmp(p->token,"vt"))
571                 {
572                         TObjVertexData *data;
573                         picoVec2_t coord;
574
575                         vertexData = SizeObjVertexData( vertexData,numUVs+1,&entries,&allocated );
576                         if (vertexData == NULL)
577                                 _obj_error_return("Realloc of vertex data failed (2)");
578
579                         data = &vertexData[ numUVs++ ];
580
581                         /* get and copy tex coord */
582                         if (!_pico_parse_vec2( p,coord ))
583                                 _obj_error_return("UV coord parse error");
584
585                         _pico_copy_vec2( coord,data->vt );
586
587 #ifdef DEBUG_PM_OBJ_EX
588                         printf("TexCoord: u: %f v: %f\n",coord[0],coord[1]);
589 #endif
590                 }
591                 /* vertex normal */
592                 else if (!_pico_stricmp(p->token,"vn"))
593                 {
594                         TObjVertexData *data;
595                         picoVec3_t n;
596
597                         vertexData = SizeObjVertexData( vertexData,numNormals+1,&entries,&allocated );
598                         if (vertexData == NULL)
599                                 _obj_error_return("Realloc of vertex data failed (3)");
600
601                         data = &vertexData[ numNormals++ ];
602
603                         /* get and copy vertex normal */
604                         if (!_pico_parse_vec( p,n ))
605                                 _obj_error_return("Vertex normal parse error");
606
607                         _pico_copy_vec( n,data->vn );
608
609 #ifdef DEBUG_PM_OBJ_EX
610                         printf("Normal: x: %f y: %f z: %f\n",n[0],n[1],n[2]);
611 #endif
612                 }
613                 /* new group (for us this means a new surface) */
614                 else if (!_pico_stricmp(p->token,"g"))
615                 {
616                         picoSurface_t *newSurface;
617                         char *groupName;
618
619                         /* get first group name (ignore 2nd,3rd,etc.) */
620                         groupName = _pico_parse( p,0 );
621                         if (groupName == NULL || !strlen(groupName))
622                         {
623                                 /* some obj exporters feel like they don't need to */
624                                 /* supply a group name. so we gotta handle it here */
625 #if 1
626                                 strcpy( p->token,"default" );
627                                 groupName = p->token;
628 #else
629                                 _obj_error_return("Invalid or missing group name");
630 #endif
631                         }
632                         /* allocate a pico surface */
633                         newSurface = PicoNewSurface( model );
634                         if (newSurface == NULL)
635                                 _obj_error_return("Error allocating surface");
636
637                         /* reset face index for surface */
638                         curFace = 0;
639
640                         /* set ptr to current surface */
641                         curSurface = newSurface;
642
643                         /* we use triangle meshes */
644                         PicoSetSurfaceType( newSurface,PICO_TRIANGLES );
645
646                         /* set surface name */
647                         PicoSetSurfaceName( newSurface,groupName );
648
649 #ifdef DEBUG_PM_OBJ_EX
650                         printf("Group: '%s'\n",groupName);
651 #endif
652                 }
653                 /* face (oh jesus, hopefully this will do the job right ;) */
654                 else if (!_pico_stricmp(p->token,"f"))
655                 {
656                         /* okay, this is a mess. some 3d apps seem to try being unique, */
657                         /* hello cinema4d & 3d exploration, feel good today?, and save */
658                         /* this crap in tons of different formats. gah, those screwed */
659                         /* coders. tho the wavefront obj standard defines exactly two */
660                         /* ways of storing face information. so, i really won't support */
661                         /* such stupid extravaganza here! */
662
663                         picoVec3_t verts  [ 4 ];
664                         picoVec3_t normals[ 4 ];
665                         picoVec2_t coords [ 4 ];
666
667                         int iv [ 4 ], has_v;
668                         int ivt[ 4 ], has_vt = 0;
669                         int ivn[ 4 ], has_vn = 0;
670                         int have_quad = 0;
671                         int slashcount;
672                         int doubleslash;
673                         int i;
674
675                         /* group defs *must* come before faces */
676                         if (curSurface == NULL)
677                                 _obj_error_return("No group defined for faces");
678
679 #ifdef DEBUG_PM_OBJ_EX
680                         printf("Face: ");
681 #endif
682                         /* read vertex/uv/normal indices for the first three face */
683                         /* vertices (cause we only support triangles) into 'i*[]' */
684                         /* store the actual vertex/uv/normal data in three arrays */
685                         /* called 'verts','coords' and 'normals'. */
686                         for (i=0; i<4; i++)
687                         {
688                                 char *str;
689
690                                 /* get next vertex index string (different */
691                                 /* formats are handled below) */
692                                 str = _pico_parse( p,0 );
693                                 if (str == NULL)
694                                 {
695                                         /* just break for quads */
696                                         if (i == 3) break;
697
698                                         /* error otherwise */
699                                         _obj_error_return("Face parse error");
700                                 }
701                                 /* if this is the fourth index string we're */
702                                 /* parsing we assume that we have a quad */
703                                 if (i == 3)
704                                         have_quad = 1;
705
706                                 /* get slash count once */
707                                 if (i == 0)
708                                 {
709                                         slashcount  = _pico_strchcount( str,'/' );
710                                         doubleslash =  strstr(str,"//") != NULL;
711                                 }
712                                 /* handle format 'v//vn' */
713                                 if (doubleslash && (slashcount == 2))
714                                 {
715                                         has_v = has_vn = 1;
716                                         sscanf( str,"%d//%d",&iv[ i ],&ivn[ i ] );
717                                 }
718                                 /* handle format 'v/vt/vn' */
719                                 else if (!doubleslash && (slashcount == 2))
720                                 {
721                                         has_v = has_vt = has_vn = 1;
722                                         sscanf( str,"%d/%d/%d",&iv[ i ],&ivt[ i ],&ivn[ i ] );
723                                 }
724                                 /* handle format 'v/vt' (non-standard fuckage) */
725                                 else if (!doubleslash && (slashcount == 1))
726                                 {
727                                         has_v = has_vt = 1;
728                                         sscanf( str,"%d/%d",&iv[ i ],&ivt[ i ] );
729                                 }
730                                 /* else assume face format 'v' */
731                                 /* (must have been invented by some bored granny) */
732                                 else {
733                                         /* get single vertex index */
734                                         has_v = 1;
735                                         iv[ i ] = atoi( str );
736
737                                         /* either invalid face format or out of range */
738                                         if (iv[ i ] == 0)
739                                                 _obj_error_return("Invalid face format");
740                                 }
741                                 /* fix useless back references */
742                                 /* todo: check if this works as it is supposed to */
743
744                                 /* assign new indices */
745                                 if (iv [ i ] < 0) iv [ i ] = (numVerts   - iv [ i ]);
746                                 if (ivt[ i ] < 0) ivt[ i ] = (numUVs     - ivt[ i ]);
747                                 if (ivn[ i ] < 0) ivn[ i ] = (numNormals - ivn[ i ]);
748
749                                 /* validate indices */
750                                 /* - commented out. index range checks will trigger
751                                 if (iv [ i ] < 1) iv [ i ] = 1;
752                                 if (ivt[ i ] < 1) ivt[ i ] = 1;
753                                 if (ivn[ i ] < 1) ivn[ i ] = 1;
754                                 */
755                                 /* set vertex origin */
756                                 if (has_v)
757                                 {
758                                         /* check vertex index range */
759                                         if (iv[ i ] < 1 || iv[ i ] > numVerts)
760                                                 _obj_error_return("Vertex index out of range");
761
762                                         /* get vertex data */
763                                         verts[ i ][ 0 ] = vertexData[ iv[ i ] - 1 ].v[ 0 ];
764                                         verts[ i ][ 1 ] = vertexData[ iv[ i ] - 1 ].v[ 1 ];
765                                         verts[ i ][ 2 ] = vertexData[ iv[ i ] - 1 ].v[ 2 ];
766                                 }
767                                 /* set vertex normal */
768                                 if (has_vn)
769                                 {
770                                         /* check normal index range */
771                                         if (ivn[ i ] < 1 || ivn[ i ] > numNormals)
772                                                 _obj_error_return("Normal index out of range");
773
774                                         /* get normal data */
775                                         normals[ i ][ 0 ] = vertexData[ ivn[ i ] - 1 ].vn[ 0 ];
776                                         normals[ i ][ 1 ] = vertexData[ ivn[ i ] - 1 ].vn[ 1 ];
777                                         normals[ i ][ 2 ] = vertexData[ ivn[ i ] - 1 ].vn[ 2 ];
778                                 }
779                                 /* set texture coordinate */
780                                 if (has_vt)
781                                 {
782                                         /* check uv index range */
783                                         if (ivt[ i ] < 1 || ivt[ i ] > numUVs)
784                                                 _obj_error_return("UV coord index out of range");
785
786                                         /* get uv coord data */
787                                         coords[ i ][ 0 ] = vertexData[ ivt[ i ] - 1 ].vt[ 0 ];
788                                         coords[ i ][ 1 ] = vertexData[ ivt[ i ] - 1 ].vt[ 1 ];
789                                         coords[ i ][ 1 ] = -coords[ i ][ 1 ];
790                                 }
791 #ifdef DEBUG_PM_OBJ_EX
792                                 printf("(%4d",iv[ i ]);
793                                 if (has_vt) printf(" %4d",ivt[ i ]);
794                                 if (has_vn) printf(" %4d",ivn[ i ]);
795                                 printf(") ");
796 #endif
797                         }
798 #ifdef DEBUG_PM_OBJ_EX
799                         printf("\n");
800 #endif
801                         /* now that we have extracted all the indices and have */
802                         /* read the actual data we need to assign all the crap */
803                         /* to our current pico surface */
804                         if (has_v)
805                         {
806                                 int max = 3;
807                                 if (have_quad) max = 4;
808
809                                 /* assign all surface information */
810                                 for (i=0; i<max; i++)
811                                 {
812                                         /*if( has_v  )*/ PicoSetSurfaceXYZ       ( curSurface,  (curVertex + i), verts  [ i ] );
813                                         /*if( has_vt )*/ PicoSetSurfaceST        ( curSurface,0,(curVertex + i), coords [ i ] );
814                                         /*if( has_vn )*/ PicoSetSurfaceNormal( curSurface,  (curVertex + i), normals[ i ] );
815                                 }
816                                 /* add our triangle (A B C) */
817                                 PicoSetSurfaceIndex( curSurface,(curFace * 3 + 2),(picoIndex_t)( curVertex + 0 ) );
818                                 PicoSetSurfaceIndex( curSurface,(curFace * 3 + 1),(picoIndex_t)( curVertex + 1 ) );
819                                 PicoSetSurfaceIndex( curSurface,(curFace * 3 + 0),(picoIndex_t)( curVertex + 2 ) );
820                                 curFace++;
821
822                                 /* if we don't have a simple triangle, but a quad... */
823                                 if (have_quad)
824                                 {
825                                         /* we have to add another triangle (2nd half of quad which is A C D) */
826                                         PicoSetSurfaceIndex( curSurface,(curFace * 3 + 2),(picoIndex_t)( curVertex + 0 ) );
827                                         PicoSetSurfaceIndex( curSurface,(curFace * 3 + 1),(picoIndex_t)( curVertex + 2 ) );
828                                         PicoSetSurfaceIndex( curSurface,(curFace * 3 + 0),(picoIndex_t)( curVertex + 3 ) );
829                                         curFace++;
830                                 }
831                                 /* increase vertex count */
832                                 curVertex += max;
833                         }
834                 }
835                 /* skip unparsed rest of line and continue */
836                 _pico_parse_skip_rest( p );
837         }
838         /* free memory used by temporary vertexdata */
839         FreeObjVertexData( vertexData );
840
841         /* return allocated pico model */
842         return model;
843 //      return NULL;
844 }
845
846 /* pico file format module definition */
847 const picoModule_t picoModuleOBJ =
848 {
849         "0.6-b",                                        /* module version string */
850         "Wavefront ASCII",                      /* module display name */
851         "seaw0lf",                                      /* author's name */
852         "2002 seaw0lf",                         /* module copyright */
853         {
854                 "obj",NULL,NULL,NULL    /* default extensions to use */
855         },
856         _obj_canload,                           /* validation routine */
857         _obj_load,                                      /* load routine */
858          NULL,                                          /* save validation routine */
859          NULL                                           /* save routine */
860 };