]> de.git.xonotic.org Git - xonotic/netradiant.git/blobdiff - libs/picomodel/pm_obj.c
eol style
[xonotic/netradiant.git] / libs / picomodel / pm_obj.c
index 2dc27e1dd5876a87d5d6e7a9e5eceadd82272265..6cb406682759b8a1372a7c700406606ae7f97a67 100644 (file)
-/* -----------------------------------------------------------------------------\r
-\r
-PicoModel Library\r
-\r
-Copyright (c) 2002, Randy Reddig & seaw0lf\r
-All rights reserved.\r
-\r
-Redistribution and use in source and binary forms, with or without modification,\r
-are permitted provided that the following conditions are met:\r
-\r
-Redistributions of source code must retain the above copyright notice, this list\r
-of conditions and the following disclaimer.\r
-\r
-Redistributions in binary form must reproduce the above copyright notice, this\r
-list of conditions and the following disclaimer in the documentation and/or\r
-other materials provided with the distribution.\r
-\r
-Neither the names of the copyright holders nor the names of its contributors may\r
-be used to endorse or promote products derived from this software without\r
-specific prior written permission.\r
-\r
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND\r
-ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\r
-WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\r
-DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR\r
-ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\r
-(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\r
-LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON\r
-ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\r
-(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\r
-SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r
-\r
------------------------------------------------------------------------------ */\r
-\r
-\r
-\r
-/* marker */\r
-#define PM_OBJ_C\r
-\r
-/* dependencies */\r
-#include "picointernal.h"\r
-\r
-/* disable warnings */\r
-#ifdef _WIN32\r
-#pragma warning( disable:4100 )                /* unref param */\r
-#endif\r
-\r
-/* todo:\r
- * - '_obj_load' code crashes in a weird way after\r
- *   '_obj_mtl_load' for a few .mtl files\r
- * - process 'mtllib' rather than using <model>.mtl\r
- * - handle 'usemtl' statements\r
- */\r
-/* uncomment when debugging this module */\r
-/* #define DEBUG_PM_OBJ */\r
-/* #define DEBUG_PM_OBJ_EX */\r
-\r
-/* this holds temporary vertex data read by parser */\r
-typedef struct SObjVertexData\r
-{\r
-       picoVec3_t      v;                      /* geometric vertices */\r
-       picoVec2_t      vt;                     /* texture vertices */\r
-       picoVec3_t      vn;                     /* vertex normals (optional) */\r
-}\r
-TObjVertexData;\r
-\r
-/* _obj_canload:\r
- *  validates a wavefront obj model file.\r
- */\r
-static int _obj_canload( PM_PARAMS_CANLOAD )\r
-{\r
-       picoParser_t *p;\r
-\r
-       /* check data length */\r
-       if (bufSize < 30)\r
-               return PICO_PMV_ERROR_SIZE;\r
-\r
-       /* first check file extension. we have to do this for objs */\r
-       /* cause there is no good way to identify the contents */\r
-       if (_pico_stristr(fileName,".obj") != NULL ||\r
-               _pico_stristr(fileName,".wf" ) != NULL)\r
-       {\r
-               return PICO_PMV_OK;\r
-       }\r
-       /* if the extension check failed we parse through the first */\r
-       /* few lines in file and look for common keywords often */\r
-       /* appearing at the beginning of wavefront objects */\r
-\r
-       /* alllocate a new pico parser */\r
-       p = _pico_new_parser( (picoByte_t *)buffer,bufSize );\r
-       if (p == NULL)\r
-               return PICO_PMV_ERROR_MEMORY;\r
-\r
-       /* parse obj head line by line for type check */\r
-       while( 1 )\r
-       {\r
-               /* get first token on line */\r
-               if (_pico_parse_first( p ) == NULL)\r
-                       break;\r
-\r
-               /* we only parse the first few lines, say 80 */\r
-               if (p->curLine > 80)\r
-                       break;\r
-\r
-               /* skip empty lines */\r
-               if (p->token == NULL || !strlen( p->token ))\r
-                       continue;\r
-\r
-               /* material library keywords are teh good */\r
-               if (!_pico_stricmp(p->token,"usemtl") ||\r
-                       !_pico_stricmp(p->token,"mtllib") ||\r
-                       !_pico_stricmp(p->token,"g") ||\r
-                       !_pico_stricmp(p->token,"v"))   /* v,g bit fishy, but uh... */\r
-               {\r
-                       /* free the pico parser thing */\r
-                       _pico_free_parser( p );\r
-\r
-                       /* seems to be a valid wavefront obj */\r
-                       return PICO_PMV_OK;\r
-               }\r
-               /* skip rest of line */\r
-               _pico_parse_skip_rest( p );\r
-       }\r
-       /* free the pico parser thing */\r
-       _pico_free_parser( p );\r
-\r
-       /* doesn't really look like an obj to us */\r
-       return PICO_PMV_ERROR;\r
-}\r
-\r
-/* SizeObjVertexData:\r
- *   This pretty piece of 'alloc ahead' code dynamically\r
- *   allocates - and reallocates as soon as required -\r
- *   my vertex data array in even steps.\r
- */\r
-#define SIZE_OBJ_STEP  4096\r
-\r
-static TObjVertexData *SizeObjVertexData(\r
-       TObjVertexData *vertexData, int reqEntries,\r
-       int *entries, int *allocated)\r
-{\r
-       int newAllocated;\r
-\r
-       /* sanity checks */\r
-       if (reqEntries < 1)\r
-               return NULL;\r
-       if (entries == NULL || allocated == NULL)\r
-               return NULL; /* must have */\r
-\r
-       /* no need to grow yet */\r
-       if (vertexData && (reqEntries < *allocated))\r
-       {\r
-               *entries = reqEntries;\r
-               return vertexData;\r
-       }\r
-       /* given vertex data ptr not allocated yet */\r
-       if (vertexData == NULL)\r
-       {\r
-               /* how many entries to allocate */\r
-               newAllocated = (reqEntries > SIZE_OBJ_STEP) ?\r
-                                               reqEntries : SIZE_OBJ_STEP;\r
-\r
-               /* throw out an extended debug message */\r
-#ifdef DEBUG_PM_OBJ_EX\r
-               printf("SizeObjVertexData: allocate (%d entries)\n",\r
-                       newAllocated);\r
-#endif\r
-               /* first time allocation */\r
-               vertexData = (TObjVertexData *)\r
-                       _pico_alloc( sizeof(TObjVertexData) * newAllocated );\r
-\r
-               /* allocation failed */\r
-               if (vertexData == NULL)\r
-                       return NULL;\r
-\r
-               /* allocation succeeded */\r
-               *allocated = newAllocated;\r
-               *entries   = reqEntries;\r
-               return vertexData;\r
-       }\r
-       /* given vertex data ptr needs to be resized */\r
-       if (reqEntries == *allocated)\r
-       {\r
-               newAllocated = (*allocated + SIZE_OBJ_STEP);\r
-\r
-               /* throw out an extended debug message */\r
-#ifdef DEBUG_PM_OBJ_EX\r
-               printf("SizeObjVertexData: reallocate (%d entries)\n",\r
-                       newAllocated);\r
-#endif\r
-               /* try to reallocate */\r
-               vertexData = (TObjVertexData *)\r
-                       _pico_realloc( (void *)&vertexData,\r
-                               sizeof(TObjVertexData) * (*allocated),\r
-                               sizeof(TObjVertexData) * (newAllocated));\r
-\r
-               /* reallocation failed */\r
-               if (vertexData == NULL)\r
-                       return NULL;\r
-\r
-               /* reallocation succeeded */\r
-               *allocated = newAllocated;\r
-               *entries   = reqEntries;\r
-               return vertexData;\r
-       }\r
-       /* we're b0rked when we reach this */\r
-       return NULL;\r
-}\r
-\r
-static void FreeObjVertexData( TObjVertexData *vertexData )\r
-{\r
-       if (vertexData != NULL)\r
-       {\r
-               free( (TObjVertexData *)vertexData );\r
-       }\r
-}\r
-\r
-static int _obj_mtl_load( picoModel_t *model )\r
-{\r
-       picoShader_t *curShader = NULL;\r
-       picoParser_t *p;\r
-       picoByte_t   *mtlBuffer;\r
-       int                       mtlBufSize;\r
-       char             *fileName;\r
-\r
-       /* sanity checks */\r
-       if( model == NULL || model->fileName == NULL )\r
-               return 0;\r
-\r
-       /* skip if we have a zero length model file name */\r
-       if (!strlen( model->fileName ))\r
-               return 0;\r
-\r
-       /* helper */\r
-       #define _obj_mtl_error_return \\r
-       { \\r
-               _pico_free_parser( p ); \\r
-               _pico_free_file( mtlBuffer ); \\r
-               _pico_free( fileName ); \\r
-               return 0; \\r
-       }\r
-       /* alloc copy of model file name */\r
-       fileName = _pico_clone_alloc( model->fileName,-1 );\r
-       if (fileName == NULL)\r
-               return 0;\r
-\r
-       /* change extension of model file to .mtl */\r
-       _pico_setfext( fileName, "mtl" );\r
-\r
-       /* load .mtl file contents */\r
-       _pico_load_file( fileName,&mtlBuffer,&mtlBufSize );\r
-\r
-       /* check result */\r
-       if (mtlBufSize == 0) return 1;          /* file is empty: no error */\r
-       if (mtlBufSize  < 0) return 0;          /* load failed: error */\r
-\r
-       /* create a new pico parser */\r
-       p = _pico_new_parser( mtlBuffer, mtlBufSize );\r
-       if (p == NULL)\r
-               _obj_mtl_error_return;\r
-       \r
-       /* doo teh .mtl parse */\r
-       while( 1 )\r
-       {\r
-               /* get next token in material file */\r
-               if (_pico_parse( p,1 ) == NULL)\r
-                       break;\r
-#if 0\r
-\r
-               /* skip empty lines */\r
-               if (p->token == NULL || !strlen( p->token ))\r
-                       continue;\r
-\r
-               /* skip comment lines */\r
-               if (p->token[0] == '#')\r
-               {\r
-                       _pico_parse_skip_rest( p );\r
-                       continue;\r
-               }\r
-               /* new material */\r
-               if (!_pico_stricmp(p->token,"newmtl"))\r
-               {\r
-                       picoShader_t *shader;\r
-                       char *name;\r
-\r
-                       /* get material name */\r
-                       name = _pico_parse( p,0 );\r
-\r
-                       /* validate material name */\r
-                       if (name == NULL || !strlen(name))\r
-                       {\r
-                               _pico_printf( PICO_ERROR,"Missing material name in MTL, line %d.",p->curLine);\r
-                               _obj_mtl_error_return;\r
-                       }\r
-                       /* create a new pico shader */\r
-                       shader = PicoNewShader( model );\r
-                       if (shader == NULL)\r
-                               _obj_mtl_error_return;\r
-\r
-                       /* set shader name */\r
-                       PicoSetShaderName( shader,name );\r
-\r
-                       /* assign pointer to current shader */\r
-                       curShader = shader;\r
-               }\r
-               /* diffuse map name */\r
-               else if (!_pico_stricmp(p->token,"map_kd"))\r
-               {\r
-                       char *mapName;\r
-\r
-                       /* pointer to current shader must be valid */\r
-                       if (curShader == NULL)\r
-                               _obj_mtl_error_return;\r
-\r
-                       /* get material's diffuse map name */\r
-                       mapName = _pico_parse( p,0 );\r
-\r
-                       /* validate map name */\r
-                       if (mapName == NULL || !strlen(mapName))\r
-                       {\r
-                               _pico_printf( PICO_ERROR,"Missing material map name in MTL, line %d.",p->curLine);\r
-                               _obj_mtl_error_return;\r
-                       }\r
-                       /* set shader map name */\r
-                       PicoSetShaderMapName( shader,mapName );\r
-               }\r
-               /* dissolve factor (pseudo transparency 0..1) */\r
-               /* where 0 means 100% transparent and 1 means opaque */\r
-               else if (!_pico_stricmp(p->token,"d"))\r
-               {\r
-                       picoByte_t *diffuse;\r
-                       float value;\r
-\r
-\r
-                       /* get dissolve factor */\r
-                       if (!_pico_parse_float( p,&value ))\r
-                               _obj_mtl_error_return;\r
-\r
-                       /* set shader transparency */\r
-                       PicoSetShaderTransparency( curShader,value );\r
-\r
-                       /* get shader's diffuse color */\r
-                       diffuse = PicoGetShaderDiffuseColor( curShader );\r
-\r
-                       /* set diffuse alpha to transparency */\r
-                       diffuse[ 3 ] = (picoByte_t)( value * 255.0 );\r
-\r
-                       /* set shader's new diffuse color */\r
-                       PicoSetShaderDiffuseColor( curShader,diffuse );\r
-               }\r
-               /* shininess (phong specular component) */\r
-               else if (!_pico_stricmp(p->token,"ns"))\r
-               {\r
-                       /* remark:\r
-                        * - well, this is some major obj spec fuckup once again. some\r
-                        *   apps store this in 0..1 range, others use 0..100 range,\r
-                        *   even others use 0..2048 range, and again others use the\r
-                        *   range 0..128, some even use 0..1000, 0..200, 400..700,\r
-                        *   honestly, what's up with the 3d app coders? happens when\r
-                        *   you smoke too much weed i guess. -sea\r
-                        */\r
-                       float value;\r
-\r
-                       /* pointer to current shader must be valid */\r
-                       if (curShader == NULL)\r
-                               _obj_mtl_error_return;\r
-\r
-                       /* get totally screwed up shininess (a random value in fact ;) */\r
-                       if (!_pico_parse_float( p,&value ))\r
-                               _obj_mtl_error_return;\r
-\r
-                       /* okay, there is no way to set this correctly, so we simply */\r
-                       /* try to guess a few ranges (most common ones i have seen) */\r
-\r
-                       /* assume 0..2048 range */\r
-                       if (value > 1000)\r
-                               value = 128.0 * (value / 2048.0);\r
-                       /* assume 0..1000 range */\r
-                       else if (value > 200)\r
-                               value = 128.0 * (value / 1000.0);\r
-                       /* assume 0..200 range */\r
-                       else if (value > 100)\r
-                               value = 128.0 * (value / 200.0);\r
-                       /* assume 0..100 range */\r
-                       else if (value > 1)\r
-                               value = 128.0 * (value / 100.0);\r
-                       /* assume 0..1 range */\r
-                       else {\r
-                               value *= 128.0;\r
-                       }\r
-                       /* negative shininess is bad (yes, i have seen it...) */\r
-                       if (value < 0.0) value = 0.0;\r
-\r
-                       /* set the pico shininess value in range 0..127 */\r
-                       /* geez, .obj is such a mess... */\r
-                       PicoSetShaderShininess( curShader,value );\r
-               }\r
-               /* kol0r ambient (wut teh fuk does "ka" stand for?) */\r
-               else if (!_pico_stricmp(p->token,"ka"))\r
-               {\r
-                       picoColor_t color;\r
-                       picoVec3_t  v;\r
-\r
-                       /* pointer to current shader must be valid */\r
-                       if (curShader == NULL)\r
-                               _obj_mtl_error_return;\r
-\r
-                       /* get color vector */\r
-                       if (!_pico_parse_vec( p,v ))\r
-                               _obj_mtl_error_return;\r
-\r
-                       /* scale to byte range */\r
-                       color[ 0 ] = (picoByte_t)( v[ 0 ] * 255 );\r
-                       color[ 1 ] = (picoByte_t)( v[ 1 ] * 255 );\r
-                       color[ 2 ] = (picoByte_t)( v[ 2 ] * 255 );\r
-                       color[ 3 ] = (picoByte_t)( 255 );\r
-\r
-                       /* set ambient color */\r
-                       PicoSetShaderAmbientColor( curShader,color );\r
-               }\r
-               /* kol0r diffuse */\r
-               else if (!_pico_stricmp(p->token,"kd"))\r
-               {\r
-                       picoColor_t color;\r
-                       picoVec3_t  v;\r
-\r
-                       /* pointer to current shader must be valid */\r
-                       if (curShader == NULL)\r
-                               _obj_mtl_error_return;\r
-\r
-                       /* get color vector */\r
-                       if (!_pico_parse_vec( p,v ))\r
-                               _obj_mtl_error_return;\r
-\r
-                       /* scale to byte range */\r
-                       color[ 0 ] = (picoByte_t)( v[ 0 ] * 255 );\r
-                       color[ 1 ] = (picoByte_t)( v[ 1 ] * 255 );\r
-                       color[ 2 ] = (picoByte_t)( v[ 2 ] * 255 );\r
-                       color[ 3 ] = (picoByte_t)( 255 );\r
-\r
-                       /* set diffuse color */\r
-                       PicoSetShaderDiffuseColor( curShader,color );\r
-               }\r
-               /* kol0r specular */\r
-               else if (!_pico_stricmp(p->token,"ks"))\r
-               {\r
-                       picoColor_t color;\r
-                       picoVec3_t  v;\r
-\r
-                       /* pointer to current shader must be valid */\r
-                       if (curShader == NULL)\r
-                               _obj_mtl_error_return;\r
-\r
-                       /* get color vector */\r
-                       if (!_pico_parse_vec( p,v ))\r
-                               _obj_mtl_error_return;\r
-\r
-                       /* scale to byte range */\r
-                       color[ 0 ] = (picoByte_t)( v[ 0 ] * 255 );\r
-                       color[ 1 ] = (picoByte_t)( v[ 1 ] * 255 );\r
-                       color[ 2 ] = (picoByte_t)( v[ 2 ] * 255 );\r
-                       color[ 3 ] = (picoByte_t)( 255 );\r
-\r
-                       /* set specular color */\r
-                       PicoSetShaderSpecularColor( curShader,color );\r
-               }\r
-#endif\r
-               /* skip rest of line */\r
-               _pico_parse_skip_rest( p );\r
-       }\r
-\r
-       /* free parser, file buffer, and file name */\r
-       _pico_free_parser( p );\r
-       _pico_free_file( mtlBuffer );\r
-       _pico_free( fileName );\r
-\r
-       /* return with success */\r
-       return 1;\r
-}\r
-\r
-/* _obj_load:\r
- *  loads a wavefront obj model file.\r
-*/\r
-static picoModel_t *_obj_load( PM_PARAMS_LOAD )\r
-{\r
-       TObjVertexData *vertexData  = NULL;\r
-       picoModel_t    *model;\r
-       picoSurface_t  *curSurface  = NULL;\r
-       picoParser_t   *p;\r
-       int                             allocated;\r
-       int                         entries;\r
-       int                             numVerts        = 0;\r
-       int                             numNormals      = 0;\r
-       int                             numUVs          = 0;\r
-       int                             curVertex       = 0;\r
-       int                             curFace         = 0;\r
-\r
-       /* helper */\r
-       #define _obj_error_return(m) \\r
-       { \\r
-               _pico_printf( PICO_ERROR,"%s in OBJ, line %d.",m,p->curLine); \\r
-               _pico_free_parser( p ); \\r
-               FreeObjVertexData( vertexData ); \\r
-               PicoFreeModel( model ); \\r
-               return NULL; \\r
-       }\r
-       /* alllocate a new pico parser */\r
-       p = _pico_new_parser( (picoByte_t *)buffer,bufSize );\r
-       if (p == NULL) return NULL;\r
-\r
-       /* create a new pico model */\r
-       model = PicoNewModel();\r
-       if (model == NULL)\r
-       {\r
-               _pico_free_parser( p );\r
-               return NULL;\r
-       }\r
-       /* do model setup */\r
-       PicoSetModelFrameNum( model,frameNum );\r
-       PicoSetModelName( model,fileName );\r
-       PicoSetModelFileName( model,fileName );\r
-\r
-       /* try loading the materials; we don't handle the result */\r
-#if 0\r
-       _obj_mtl_load( model );\r
-#endif\r
-\r
-       /* parse obj line by line */\r
-       while( 1 )\r
-       {\r
-               /* get first token on line */\r
-               if (_pico_parse_first( p ) == NULL)\r
-                       break;\r
-\r
-               /* skip empty lines */\r
-               if (p->token == NULL || !strlen( p->token ))\r
-                       continue;\r
-\r
-               /* skip comment lines */\r
-               if (p->token[0] == '#')\r
-               {\r
-                       _pico_parse_skip_rest( p );\r
-                       continue;\r
-               }\r
-               /* vertex */\r
-               if (!_pico_stricmp(p->token,"v"))\r
-               {\r
-                       TObjVertexData *data;\r
-                       picoVec3_t v;\r
-\r
-                       vertexData = SizeObjVertexData( vertexData,numVerts+1,&entries,&allocated );\r
-                       if (vertexData == NULL)\r
-                               _obj_error_return("Realloc of vertex data failed (1)");\r
-\r
-                       data = &vertexData[ numVerts++ ];\r
-\r
-                       /* get and copy vertex */\r
-                       if (!_pico_parse_vec( p,v ))\r
-                               _obj_error_return("Vertex parse error");\r
-\r
-                       _pico_copy_vec( v,data->v );\r
-\r
-#ifdef DEBUG_PM_OBJ_EX\r
-                       printf("Vertex: x: %f y: %f z: %f\n",v[0],v[1],v[2]);\r
-#endif\r
-               }\r
-               /* uv coord */\r
-               else if (!_pico_stricmp(p->token,"vt"))\r
-               {\r
-                       TObjVertexData *data;\r
-                       picoVec2_t coord;\r
-\r
-                       vertexData = SizeObjVertexData( vertexData,numUVs+1,&entries,&allocated );\r
-                       if (vertexData == NULL)\r
-                               _obj_error_return("Realloc of vertex data failed (2)");\r
-\r
-                       data = &vertexData[ numUVs++ ];\r
-\r
-                       /* get and copy tex coord */\r
-                       if (!_pico_parse_vec2( p,coord ))\r
-                               _obj_error_return("UV coord parse error");\r
-\r
-                       _pico_copy_vec2( coord,data->vt );\r
-\r
-#ifdef DEBUG_PM_OBJ_EX\r
-                       printf("TexCoord: u: %f v: %f\n",coord[0],coord[1]);\r
-#endif\r
-               }\r
-               /* vertex normal */\r
-               else if (!_pico_stricmp(p->token,"vn"))\r
-               {\r
-                       TObjVertexData *data;\r
-                       picoVec3_t n;\r
-\r
-                       vertexData = SizeObjVertexData( vertexData,numNormals+1,&entries,&allocated );\r
-                       if (vertexData == NULL)\r
-                               _obj_error_return("Realloc of vertex data failed (3)");\r
-\r
-                       data = &vertexData[ numNormals++ ];\r
-\r
-                       /* get and copy vertex normal */\r
-                       if (!_pico_parse_vec( p,n ))\r
-                               _obj_error_return("Vertex normal parse error");\r
-\r
-                       _pico_copy_vec( n,data->vn );\r
-\r
-#ifdef DEBUG_PM_OBJ_EX\r
-                       printf("Normal: x: %f y: %f z: %f\n",n[0],n[1],n[2]);\r
-#endif\r
-               }\r
-               /* new group (for us this means a new surface) */\r
-               else if (!_pico_stricmp(p->token,"g"))\r
-               {\r
-                       picoSurface_t *newSurface;\r
-                       char *groupName;\r
-\r
-                       /* get first group name (ignore 2nd,3rd,etc.) */\r
-                       groupName = _pico_parse( p,0 );\r
-                       if (groupName == NULL || !strlen(groupName))\r
-                       {\r
-                               /* some obj exporters feel like they don't need to */\r
-                               /* supply a group name. so we gotta handle it here */\r
-#if 1\r
-                               strcpy( p->token,"default" );\r
-                               groupName = p->token;\r
-#else\r
-                               _obj_error_return("Invalid or missing group name");\r
-#endif\r
-                       }\r
-                       /* allocate a pico surface */\r
-                       newSurface = PicoNewSurface( model );\r
-                       if (newSurface == NULL)\r
-                               _obj_error_return("Error allocating surface");\r
-\r
-                       /* reset face index for surface */\r
-                       curFace = 0;\r
-\r
-                       /* set ptr to current surface */\r
-                       curSurface = newSurface;\r
-\r
-                       /* we use triangle meshes */\r
-                       PicoSetSurfaceType( newSurface,PICO_TRIANGLES );\r
-\r
-                       /* set surface name */\r
-                       PicoSetSurfaceName( newSurface,groupName );\r
-\r
-#ifdef DEBUG_PM_OBJ_EX\r
-                       printf("Group: '%s'\n",groupName);\r
-#endif\r
-               }\r
-               /* face (oh jesus, hopefully this will do the job right ;) */\r
-               else if (!_pico_stricmp(p->token,"f"))\r
-               {\r
-                       /* okay, this is a mess. some 3d apps seem to try being unique, */\r
-                       /* hello cinema4d & 3d exploration, feel good today?, and save */\r
-                       /* this crap in tons of different formats. gah, those screwed */\r
-                       /* coders. tho the wavefront obj standard defines exactly two */\r
-                       /* ways of storing face information. so, i really won't support */\r
-                       /* such stupid extravaganza here! */\r
-\r
-                       picoVec3_t verts  [ 4 ];\r
-                       picoVec3_t normals[ 4 ];\r
-                       picoVec2_t coords [ 4 ];\r
-\r
-                       int iv [ 4 ], has_v;\r
-                       int ivt[ 4 ], has_vt = 0;\r
-                       int ivn[ 4 ], has_vn = 0;\r
-                       int have_quad = 0;\r
-                       int slashcount;\r
-                       int doubleslash;\r
-                       int i;\r
-\r
-                       /* group defs *must* come before faces */\r
-                       if (curSurface == NULL)\r
-                               _obj_error_return("No group defined for faces");\r
-\r
-#ifdef DEBUG_PM_OBJ_EX\r
-                       printf("Face: ");\r
-#endif\r
-                       /* read vertex/uv/normal indices for the first three face */\r
-                       /* vertices (cause we only support triangles) into 'i*[]' */\r
-                       /* store the actual vertex/uv/normal data in three arrays */\r
-                       /* called 'verts','coords' and 'normals'. */\r
-                       for (i=0; i<4; i++)\r
-                       {\r
-                               char *str;\r
-\r
-                               /* get next vertex index string (different */\r
-                               /* formats are handled below) */\r
-                               str = _pico_parse( p,0 );\r
-                               if (str == NULL)\r
-                               {\r
-                                       /* just break for quads */\r
-                                       if (i == 3) break;\r
-\r
-                                       /* error otherwise */\r
-                                       _obj_error_return("Face parse error");\r
-                               }\r
-                               /* if this is the fourth index string we're */\r
-                               /* parsing we assume that we have a quad */\r
-                               if (i == 3)\r
-                                       have_quad = 1;\r
-\r
-                               /* get slash count once */\r
-                               if (i == 0)\r
-                               {\r
-                                       slashcount  = _pico_strchcount( str,'/' );\r
-                                       doubleslash =  strstr(str,"//") != NULL;\r
-                               }\r
-                               /* handle format 'v//vn' */\r
-                               if (doubleslash && (slashcount == 2))\r
-                               {\r
-                                       has_v = has_vn = 1;\r
-                                       sscanf( str,"%d//%d",&iv[ i ],&ivn[ i ] );\r
-                               }\r
-                               /* handle format 'v/vt/vn' */\r
-                               else if (!doubleslash && (slashcount == 2))\r
-                               {\r
-                                       has_v = has_vt = has_vn = 1;\r
-                                       sscanf( str,"%d/%d/%d",&iv[ i ],&ivt[ i ],&ivn[ i ] );\r
-                               }\r
-                               /* handle format 'v/vt' (non-standard fuckage) */\r
-                               else if (!doubleslash && (slashcount == 1))\r
-                               {\r
-                                       has_v = has_vt = 1;\r
-                                       sscanf( str,"%d/%d",&iv[ i ],&ivt[ i ] );\r
-                               }\r
-                               /* else assume face format 'v' */\r
-                               /* (must have been invented by some bored granny) */\r
-                               else {\r
-                                       /* get single vertex index */\r
-                                       has_v = 1;\r
-                                       iv[ i ] = atoi( str );\r
-\r
-                                       /* either invalid face format or out of range */\r
-                                       if (iv[ i ] == 0)\r
-                                               _obj_error_return("Invalid face format");\r
-                               }\r
-                               /* fix useless back references */\r
-                               /* todo: check if this works as it is supposed to */\r
-\r
-                               /* assign new indices */\r
-                               if (iv [ i ] < 0) iv [ i ] = (numVerts   - iv [ i ]);\r
-                               if (ivt[ i ] < 0) ivt[ i ] = (numUVs     - ivt[ i ]);\r
-                               if (ivn[ i ] < 0) ivn[ i ] = (numNormals - ivn[ i ]);\r
-\r
-                               /* validate indices */\r
-                               /* - commented out. index range checks will trigger\r
-                               if (iv [ i ] < 1) iv [ i ] = 1;\r
-                               if (ivt[ i ] < 1) ivt[ i ] = 1;\r
-                               if (ivn[ i ] < 1) ivn[ i ] = 1;\r
-                               */\r
-                               /* set vertex origin */\r
-                               if (has_v)\r
-                               {\r
-                                       /* check vertex index range */\r
-                                       if (iv[ i ] < 1 || iv[ i ] > numVerts)\r
-                                               _obj_error_return("Vertex index out of range");\r
-\r
-                                       /* get vertex data */\r
-                                       verts[ i ][ 0 ] = vertexData[ iv[ i ] - 1 ].v[ 0 ];\r
-                                       verts[ i ][ 1 ] = vertexData[ iv[ i ] - 1 ].v[ 1 ];\r
-                                       verts[ i ][ 2 ] = vertexData[ iv[ i ] - 1 ].v[ 2 ];\r
-                               }\r
-                               /* set vertex normal */\r
-                               if (has_vn)\r
-                               {\r
-                                       /* check normal index range */\r
-                                       if (ivn[ i ] < 1 || ivn[ i ] > numNormals)\r
-                                               _obj_error_return("Normal index out of range");\r
-\r
-                                       /* get normal data */\r
-                                       normals[ i ][ 0 ] = vertexData[ ivn[ i ] - 1 ].vn[ 0 ];\r
-                                       normals[ i ][ 1 ] = vertexData[ ivn[ i ] - 1 ].vn[ 1 ];\r
-                                       normals[ i ][ 2 ] = vertexData[ ivn[ i ] - 1 ].vn[ 2 ];\r
-                               }\r
-                               /* set texture coordinate */\r
-                               if (has_vt)\r
-                               {\r
-                                       /* check uv index range */\r
-                                       if (ivt[ i ] < 1 || ivt[ i ] > numUVs)\r
-                                               _obj_error_return("UV coord index out of range");\r
-\r
-                                       /* get uv coord data */\r
-                                       coords[ i ][ 0 ] = vertexData[ ivt[ i ] - 1 ].vt[ 0 ];\r
-                                       coords[ i ][ 1 ] = vertexData[ ivt[ i ] - 1 ].vt[ 1 ];\r
-                                       coords[ i ][ 1 ] = -coords[ i ][ 1 ];\r
-                               }\r
-#ifdef DEBUG_PM_OBJ_EX\r
-                               printf("(%4d",iv[ i ]);\r
-                               if (has_vt) printf(" %4d",ivt[ i ]);\r
-                               if (has_vn) printf(" %4d",ivn[ i ]);\r
-                               printf(") ");\r
-#endif\r
-                       }\r
-#ifdef DEBUG_PM_OBJ_EX\r
-                       printf("\n");\r
-#endif\r
-                       /* now that we have extracted all the indices and have */\r
-                       /* read the actual data we need to assign all the crap */\r
-                       /* to our current pico surface */\r
-                       if (has_v)\r
-                       {\r
-                               int max = 3;\r
-                               if (have_quad) max = 4;\r
-\r
-                               /* assign all surface information */\r
-                               for (i=0; i<max; i++)\r
-                               {\r
-                                       /*if( has_v  )*/ PicoSetSurfaceXYZ       ( curSurface,  (curVertex + i), verts  [ i ] );\r
-                                       /*if( has_vt )*/ PicoSetSurfaceST        ( curSurface,0,(curVertex + i), coords [ i ] );\r
-                                       /*if( has_vn )*/ PicoSetSurfaceNormal( curSurface,  (curVertex + i), normals[ i ] );\r
-                               }\r
-                               /* add our triangle (A B C) */\r
-                               PicoSetSurfaceIndex( curSurface,(curFace * 3 + 2),(picoIndex_t)( curVertex + 0 ) );\r
-                               PicoSetSurfaceIndex( curSurface,(curFace * 3 + 1),(picoIndex_t)( curVertex + 1 ) );\r
-                               PicoSetSurfaceIndex( curSurface,(curFace * 3 + 0),(picoIndex_t)( curVertex + 2 ) );\r
-                               curFace++;\r
-\r
-                               /* if we don't have a simple triangle, but a quad... */\r
-                               if (have_quad)\r
-                               {\r
-                                       /* we have to add another triangle (2nd half of quad which is A C D) */\r
-                                       PicoSetSurfaceIndex( curSurface,(curFace * 3 + 2),(picoIndex_t)( curVertex + 0 ) );\r
-                                       PicoSetSurfaceIndex( curSurface,(curFace * 3 + 1),(picoIndex_t)( curVertex + 2 ) );\r
-                                       PicoSetSurfaceIndex( curSurface,(curFace * 3 + 0),(picoIndex_t)( curVertex + 3 ) );\r
-                                       curFace++;\r
-                               }\r
-                               /* increase vertex count */\r
-                               curVertex += max;\r
-                       }\r
-               }\r
-               /* skip unparsed rest of line and continue */\r
-               _pico_parse_skip_rest( p );\r
-       }\r
-       /* free memory used by temporary vertexdata */\r
-       FreeObjVertexData( vertexData );\r
-\r
-       /* return allocated pico model */\r
-       return model;\r
-//     return NULL;\r
-}\r
-\r
-/* pico file format module definition */\r
-const picoModule_t picoModuleOBJ =\r
-{\r
-       "0.6-b",                                        /* module version string */\r
-       "Wavefront ASCII",                      /* module display name */\r
-       "seaw0lf",                                      /* author's name */\r
-       "2002 seaw0lf",                         /* module copyright */\r
-       {\r
-               "obj",NULL,NULL,NULL    /* default extensions to use */\r
-       },\r
-       _obj_canload,                           /* validation routine */\r
-       _obj_load,                                      /* load routine */\r
-        NULL,                                          /* save validation routine */\r
-        NULL                                           /* save routine */\r
-};\r
+/* -----------------------------------------------------------------------------
+
+PicoModel Library
+
+Copyright (c) 2002, Randy Reddig & seaw0lf
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+Redistributions of source code must retain the above copyright notice, this list
+of conditions and the following disclaimer.
+
+Redistributions in binary form must reproduce the above copyright notice, this
+list of conditions and the following disclaimer in the documentation and/or
+other materials provided with the distribution.
+
+Neither the names of the copyright holders nor the names of its contributors may
+be used to endorse or promote products derived from this software without
+specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------------- */
+
+
+
+/* marker */
+#define PM_OBJ_C
+
+/* dependencies */
+#include "picointernal.h"
+
+/* disable warnings */
+#ifdef _WIN32
+#pragma warning( disable:4100 )                /* unref param */
+#endif
+
+/* todo:
+ * - '_obj_load' code crashes in a weird way after
+ *   '_obj_mtl_load' for a few .mtl files
+ * - process 'mtllib' rather than using <model>.mtl
+ * - handle 'usemtl' statements
+ */
+/* uncomment when debugging this module */
+/* #define DEBUG_PM_OBJ */
+/* #define DEBUG_PM_OBJ_EX */
+
+/* this holds temporary vertex data read by parser */
+typedef struct SObjVertexData
+{
+       picoVec3_t      v;                      /* geometric vertices */
+       picoVec2_t      vt;                     /* texture vertices */
+       picoVec3_t      vn;                     /* vertex normals (optional) */
+}
+TObjVertexData;
+
+/* _obj_canload:
+ *  validates a wavefront obj model file.
+ */
+static int _obj_canload( PM_PARAMS_CANLOAD )
+{
+       picoParser_t *p;
+
+       /* check data length */
+       if (bufSize < 30)
+               return PICO_PMV_ERROR_SIZE;
+
+       /* first check file extension. we have to do this for objs */
+       /* cause there is no good way to identify the contents */
+       if (_pico_stristr(fileName,".obj") != NULL ||
+               _pico_stristr(fileName,".wf" ) != NULL)
+       {
+               return PICO_PMV_OK;
+       }
+       /* if the extension check failed we parse through the first */
+       /* few lines in file and look for common keywords often */
+       /* appearing at the beginning of wavefront objects */
+
+       /* alllocate a new pico parser */
+       p = _pico_new_parser( (picoByte_t *)buffer,bufSize );
+       if (p == NULL)
+               return PICO_PMV_ERROR_MEMORY;
+
+       /* parse obj head line by line for type check */
+       while( 1 )
+       {
+               /* get first token on line */
+               if (_pico_parse_first( p ) == NULL)
+                       break;
+
+               /* we only parse the first few lines, say 80 */
+               if (p->curLine > 80)
+                       break;
+
+               /* skip empty lines */
+               if (p->token == NULL || !strlen( p->token ))
+                       continue;
+
+               /* material library keywords are teh good */
+               if (!_pico_stricmp(p->token,"usemtl") ||
+                       !_pico_stricmp(p->token,"mtllib") ||
+                       !_pico_stricmp(p->token,"g") ||
+                       !_pico_stricmp(p->token,"v"))   /* v,g bit fishy, but uh... */
+               {
+                       /* free the pico parser thing */
+                       _pico_free_parser( p );
+
+                       /* seems to be a valid wavefront obj */
+                       return PICO_PMV_OK;
+               }
+               /* skip rest of line */
+               _pico_parse_skip_rest( p );
+       }
+       /* free the pico parser thing */
+       _pico_free_parser( p );
+
+       /* doesn't really look like an obj to us */
+       return PICO_PMV_ERROR;
+}
+
+/* SizeObjVertexData:
+ *   This pretty piece of 'alloc ahead' code dynamically
+ *   allocates - and reallocates as soon as required -
+ *   my vertex data array in even steps.
+ */
+#define SIZE_OBJ_STEP  4096
+
+static TObjVertexData *SizeObjVertexData(
+       TObjVertexData *vertexData, int reqEntries,
+       int *entries, int *allocated)
+{
+       int newAllocated;
+
+       /* sanity checks */
+       if (reqEntries < 1)
+               return NULL;
+       if (entries == NULL || allocated == NULL)
+               return NULL; /* must have */
+
+       /* no need to grow yet */
+       if (vertexData && (reqEntries < *allocated))
+       {
+               *entries = reqEntries;
+               return vertexData;
+       }
+       /* given vertex data ptr not allocated yet */
+       if (vertexData == NULL)
+       {
+               /* how many entries to allocate */
+               newAllocated = (reqEntries > SIZE_OBJ_STEP) ?
+                                               reqEntries : SIZE_OBJ_STEP;
+
+               /* throw out an extended debug message */
+#ifdef DEBUG_PM_OBJ_EX
+               printf("SizeObjVertexData: allocate (%d entries)\n",
+                       newAllocated);
+#endif
+               /* first time allocation */
+               vertexData = (TObjVertexData *)
+                       _pico_alloc( sizeof(TObjVertexData) * newAllocated );
+
+               /* allocation failed */
+               if (vertexData == NULL)
+                       return NULL;
+
+               /* allocation succeeded */
+               *allocated = newAllocated;
+               *entries   = reqEntries;
+               return vertexData;
+       }
+       /* given vertex data ptr needs to be resized */
+       if (reqEntries == *allocated)
+       {
+               newAllocated = (*allocated + SIZE_OBJ_STEP);
+
+               /* throw out an extended debug message */
+#ifdef DEBUG_PM_OBJ_EX
+               printf("SizeObjVertexData: reallocate (%d entries)\n",
+                       newAllocated);
+#endif
+               /* try to reallocate */
+               vertexData = (TObjVertexData *)
+                       _pico_realloc( (void *)&vertexData,
+                               sizeof(TObjVertexData) * (*allocated),
+                               sizeof(TObjVertexData) * (newAllocated));
+
+               /* reallocation failed */
+               if (vertexData == NULL)
+                       return NULL;
+
+               /* reallocation succeeded */
+               *allocated = newAllocated;
+               *entries   = reqEntries;
+               return vertexData;
+       }
+       /* we're b0rked when we reach this */
+       return NULL;
+}
+
+static void FreeObjVertexData( TObjVertexData *vertexData )
+{
+       if (vertexData != NULL)
+       {
+               free( (TObjVertexData *)vertexData );
+       }
+}
+
+static int _obj_mtl_load( picoModel_t *model )
+{
+       picoShader_t *curShader = NULL;
+       picoParser_t *p;
+       picoByte_t   *mtlBuffer;
+       int                       mtlBufSize;
+       char             *fileName;
+
+       /* sanity checks */
+       if( model == NULL || model->fileName == NULL )
+               return 0;
+
+       /* skip if we have a zero length model file name */
+       if (!strlen( model->fileName ))
+               return 0;
+
+       /* helper */
+       #define _obj_mtl_error_return \
+       { \
+               _pico_free_parser( p ); \
+               _pico_free_file( mtlBuffer ); \
+               _pico_free( fileName ); \
+               return 0; \
+       }
+       /* alloc copy of model file name */
+       fileName = _pico_clone_alloc( model->fileName,-1 );
+       if (fileName == NULL)
+               return 0;
+
+       /* change extension of model file to .mtl */
+       _pico_setfext( fileName, "mtl" );
+
+       /* load .mtl file contents */
+       _pico_load_file( fileName,&mtlBuffer,&mtlBufSize );
+
+       /* check result */
+       if (mtlBufSize == 0) return 1;          /* file is empty: no error */
+       if (mtlBufSize  < 0) return 0;          /* load failed: error */
+
+       /* create a new pico parser */
+       p = _pico_new_parser( mtlBuffer, mtlBufSize );
+       if (p == NULL)
+               _obj_mtl_error_return;
+       
+       /* doo teh .mtl parse */
+       while( 1 )
+       {
+               /* get next token in material file */
+               if (_pico_parse( p,1 ) == NULL)
+                       break;
+#if 0
+
+               /* skip empty lines */
+               if (p->token == NULL || !strlen( p->token ))
+                       continue;
+
+               /* skip comment lines */
+               if (p->token[0] == '#')
+               {
+                       _pico_parse_skip_rest( p );
+                       continue;
+               }
+               /* new material */
+               if (!_pico_stricmp(p->token,"newmtl"))
+               {
+                       picoShader_t *shader;
+                       char *name;
+
+                       /* get material name */
+                       name = _pico_parse( p,0 );
+
+                       /* validate material name */
+                       if (name == NULL || !strlen(name))
+                       {
+                               _pico_printf( PICO_ERROR,"Missing material name in MTL, line %d.",p->curLine);
+                               _obj_mtl_error_return;
+                       }
+                       /* create a new pico shader */
+                       shader = PicoNewShader( model );
+                       if (shader == NULL)
+                               _obj_mtl_error_return;
+
+                       /* set shader name */
+                       PicoSetShaderName( shader,name );
+
+                       /* assign pointer to current shader */
+                       curShader = shader;
+               }
+               /* diffuse map name */
+               else if (!_pico_stricmp(p->token,"map_kd"))
+               {
+                       char *mapName;
+
+                       /* pointer to current shader must be valid */
+                       if (curShader == NULL)
+                               _obj_mtl_error_return;
+
+                       /* get material's diffuse map name */
+                       mapName = _pico_parse( p,0 );
+
+                       /* validate map name */
+                       if (mapName == NULL || !strlen(mapName))
+                       {
+                               _pico_printf( PICO_ERROR,"Missing material map name in MTL, line %d.",p->curLine);
+                               _obj_mtl_error_return;
+                       }
+                       /* set shader map name */
+                       PicoSetShaderMapName( shader,mapName );
+               }
+               /* dissolve factor (pseudo transparency 0..1) */
+               /* where 0 means 100% transparent and 1 means opaque */
+               else if (!_pico_stricmp(p->token,"d"))
+               {
+                       picoByte_t *diffuse;
+                       float value;
+
+
+                       /* get dissolve factor */
+                       if (!_pico_parse_float( p,&value ))
+                               _obj_mtl_error_return;
+
+                       /* set shader transparency */
+                       PicoSetShaderTransparency( curShader,value );
+
+                       /* get shader's diffuse color */
+                       diffuse = PicoGetShaderDiffuseColor( curShader );
+
+                       /* set diffuse alpha to transparency */
+                       diffuse[ 3 ] = (picoByte_t)( value * 255.0 );
+
+                       /* set shader's new diffuse color */
+                       PicoSetShaderDiffuseColor( curShader,diffuse );
+               }
+               /* shininess (phong specular component) */
+               else if (!_pico_stricmp(p->token,"ns"))
+               {
+                       /* remark:
+                        * - well, this is some major obj spec fuckup once again. some
+                        *   apps store this in 0..1 range, others use 0..100 range,
+                        *   even others use 0..2048 range, and again others use the
+                        *   range 0..128, some even use 0..1000, 0..200, 400..700,
+                        *   honestly, what's up with the 3d app coders? happens when
+                        *   you smoke too much weed i guess. -sea
+                        */
+                       float value;
+
+                       /* pointer to current shader must be valid */
+                       if (curShader == NULL)
+                               _obj_mtl_error_return;
+
+                       /* get totally screwed up shininess (a random value in fact ;) */
+                       if (!_pico_parse_float( p,&value ))
+                               _obj_mtl_error_return;
+
+                       /* okay, there is no way to set this correctly, so we simply */
+                       /* try to guess a few ranges (most common ones i have seen) */
+
+                       /* assume 0..2048 range */
+                       if (value > 1000)
+                               value = 128.0 * (value / 2048.0);
+                       /* assume 0..1000 range */
+                       else if (value > 200)
+                               value = 128.0 * (value / 1000.0);
+                       /* assume 0..200 range */
+                       else if (value > 100)
+                               value = 128.0 * (value / 200.0);
+                       /* assume 0..100 range */
+                       else if (value > 1)
+                               value = 128.0 * (value / 100.0);
+                       /* assume 0..1 range */
+                       else {
+                               value *= 128.0;
+                       }
+                       /* negative shininess is bad (yes, i have seen it...) */
+                       if (value < 0.0) value = 0.0;
+
+                       /* set the pico shininess value in range 0..127 */
+                       /* geez, .obj is such a mess... */
+                       PicoSetShaderShininess( curShader,value );
+               }
+               /* kol0r ambient (wut teh fuk does "ka" stand for?) */
+               else if (!_pico_stricmp(p->token,"ka"))
+               {
+                       picoColor_t color;
+                       picoVec3_t  v;
+
+                       /* pointer to current shader must be valid */
+                       if (curShader == NULL)
+                               _obj_mtl_error_return;
+
+                       /* get color vector */
+                       if (!_pico_parse_vec( p,v ))
+                               _obj_mtl_error_return;
+
+                       /* scale to byte range */
+                       color[ 0 ] = (picoByte_t)( v[ 0 ] * 255 );
+                       color[ 1 ] = (picoByte_t)( v[ 1 ] * 255 );
+                       color[ 2 ] = (picoByte_t)( v[ 2 ] * 255 );
+                       color[ 3 ] = (picoByte_t)( 255 );
+
+                       /* set ambient color */
+                       PicoSetShaderAmbientColor( curShader,color );
+               }
+               /* kol0r diffuse */
+               else if (!_pico_stricmp(p->token,"kd"))
+               {
+                       picoColor_t color;
+                       picoVec3_t  v;
+
+                       /* pointer to current shader must be valid */
+                       if (curShader == NULL)
+                               _obj_mtl_error_return;
+
+                       /* get color vector */
+                       if (!_pico_parse_vec( p,v ))
+                               _obj_mtl_error_return;
+
+                       /* scale to byte range */
+                       color[ 0 ] = (picoByte_t)( v[ 0 ] * 255 );
+                       color[ 1 ] = (picoByte_t)( v[ 1 ] * 255 );
+                       color[ 2 ] = (picoByte_t)( v[ 2 ] * 255 );
+                       color[ 3 ] = (picoByte_t)( 255 );
+
+                       /* set diffuse color */
+                       PicoSetShaderDiffuseColor( curShader,color );
+               }
+               /* kol0r specular */
+               else if (!_pico_stricmp(p->token,"ks"))
+               {
+                       picoColor_t color;
+                       picoVec3_t  v;
+
+                       /* pointer to current shader must be valid */
+                       if (curShader == NULL)
+                               _obj_mtl_error_return;
+
+                       /* get color vector */
+                       if (!_pico_parse_vec( p,v ))
+                               _obj_mtl_error_return;
+
+                       /* scale to byte range */
+                       color[ 0 ] = (picoByte_t)( v[ 0 ] * 255 );
+                       color[ 1 ] = (picoByte_t)( v[ 1 ] * 255 );
+                       color[ 2 ] = (picoByte_t)( v[ 2 ] * 255 );
+                       color[ 3 ] = (picoByte_t)( 255 );
+
+                       /* set specular color */
+                       PicoSetShaderSpecularColor( curShader,color );
+               }
+#endif
+               /* skip rest of line */
+               _pico_parse_skip_rest( p );
+       }
+
+       /* free parser, file buffer, and file name */
+       _pico_free_parser( p );
+       _pico_free_file( mtlBuffer );
+       _pico_free( fileName );
+
+       /* return with success */
+       return 1;
+}
+
+/* _obj_load:
+ *  loads a wavefront obj model file.
+*/
+static picoModel_t *_obj_load( PM_PARAMS_LOAD )
+{
+       TObjVertexData *vertexData  = NULL;
+       picoModel_t    *model;
+       picoSurface_t  *curSurface  = NULL;
+       picoParser_t   *p;
+       int                             allocated;
+       int                         entries;
+       int                             numVerts        = 0;
+       int                             numNormals      = 0;
+       int                             numUVs          = 0;
+       int                             curVertex       = 0;
+       int                             curFace         = 0;
+
+       /* helper */
+       #define _obj_error_return(m) \
+       { \
+               _pico_printf( PICO_ERROR,"%s in OBJ, line %d.",m,p->curLine); \
+               _pico_free_parser( p ); \
+               FreeObjVertexData( vertexData ); \
+               PicoFreeModel( model ); \
+               return NULL; \
+       }
+       /* alllocate a new pico parser */
+       p = _pico_new_parser( (picoByte_t *)buffer,bufSize );
+       if (p == NULL) return NULL;
+
+       /* create a new pico model */
+       model = PicoNewModel();
+       if (model == NULL)
+       {
+               _pico_free_parser( p );
+               return NULL;
+       }
+       /* do model setup */
+       PicoSetModelFrameNum( model,frameNum );
+       PicoSetModelName( model,fileName );
+       PicoSetModelFileName( model,fileName );
+
+       /* try loading the materials; we don't handle the result */
+#if 0
+       _obj_mtl_load( model );
+#endif
+
+       /* parse obj line by line */
+       while( 1 )
+       {
+               /* get first token on line */
+               if (_pico_parse_first( p ) == NULL)
+                       break;
+
+               /* skip empty lines */
+               if (p->token == NULL || !strlen( p->token ))
+                       continue;
+
+               /* skip comment lines */
+               if (p->token[0] == '#')
+               {
+                       _pico_parse_skip_rest( p );
+                       continue;
+               }
+               /* vertex */
+               if (!_pico_stricmp(p->token,"v"))
+               {
+                       TObjVertexData *data;
+                       picoVec3_t v;
+
+                       vertexData = SizeObjVertexData( vertexData,numVerts+1,&entries,&allocated );
+                       if (vertexData == NULL)
+                               _obj_error_return("Realloc of vertex data failed (1)");
+
+                       data = &vertexData[ numVerts++ ];
+
+                       /* get and copy vertex */
+                       if (!_pico_parse_vec( p,v ))
+                               _obj_error_return("Vertex parse error");
+
+                       _pico_copy_vec( v,data->v );
+
+#ifdef DEBUG_PM_OBJ_EX
+                       printf("Vertex: x: %f y: %f z: %f\n",v[0],v[1],v[2]);
+#endif
+               }
+               /* uv coord */
+               else if (!_pico_stricmp(p->token,"vt"))
+               {
+                       TObjVertexData *data;
+                       picoVec2_t coord;
+
+                       vertexData = SizeObjVertexData( vertexData,numUVs+1,&entries,&allocated );
+                       if (vertexData == NULL)
+                               _obj_error_return("Realloc of vertex data failed (2)");
+
+                       data = &vertexData[ numUVs++ ];
+
+                       /* get and copy tex coord */
+                       if (!_pico_parse_vec2( p,coord ))
+                               _obj_error_return("UV coord parse error");
+
+                       _pico_copy_vec2( coord,data->vt );
+
+#ifdef DEBUG_PM_OBJ_EX
+                       printf("TexCoord: u: %f v: %f\n",coord[0],coord[1]);
+#endif
+               }
+               /* vertex normal */
+               else if (!_pico_stricmp(p->token,"vn"))
+               {
+                       TObjVertexData *data;
+                       picoVec3_t n;
+
+                       vertexData = SizeObjVertexData( vertexData,numNormals+1,&entries,&allocated );
+                       if (vertexData == NULL)
+                               _obj_error_return("Realloc of vertex data failed (3)");
+
+                       data = &vertexData[ numNormals++ ];
+
+                       /* get and copy vertex normal */
+                       if (!_pico_parse_vec( p,n ))
+                               _obj_error_return("Vertex normal parse error");
+
+                       _pico_copy_vec( n,data->vn );
+
+#ifdef DEBUG_PM_OBJ_EX
+                       printf("Normal: x: %f y: %f z: %f\n",n[0],n[1],n[2]);
+#endif
+               }
+               /* new group (for us this means a new surface) */
+               else if (!_pico_stricmp(p->token,"g"))
+               {
+                       picoSurface_t *newSurface;
+                       char *groupName;
+
+                       /* get first group name (ignore 2nd,3rd,etc.) */
+                       groupName = _pico_parse( p,0 );
+                       if (groupName == NULL || !strlen(groupName))
+                       {
+                               /* some obj exporters feel like they don't need to */
+                               /* supply a group name. so we gotta handle it here */
+#if 1
+                               strcpy( p->token,"default" );
+                               groupName = p->token;
+#else
+                               _obj_error_return("Invalid or missing group name");
+#endif
+                       }
+                       /* allocate a pico surface */
+                       newSurface = PicoNewSurface( model );
+                       if (newSurface == NULL)
+                               _obj_error_return("Error allocating surface");
+
+                       /* reset face index for surface */
+                       curFace = 0;
+
+                       /* set ptr to current surface */
+                       curSurface = newSurface;
+
+                       /* we use triangle meshes */
+                       PicoSetSurfaceType( newSurface,PICO_TRIANGLES );
+
+                       /* set surface name */
+                       PicoSetSurfaceName( newSurface,groupName );
+
+#ifdef DEBUG_PM_OBJ_EX
+                       printf("Group: '%s'\n",groupName);
+#endif
+               }
+               /* face (oh jesus, hopefully this will do the job right ;) */
+               else if (!_pico_stricmp(p->token,"f"))
+               {
+                       /* okay, this is a mess. some 3d apps seem to try being unique, */
+                       /* hello cinema4d & 3d exploration, feel good today?, and save */
+                       /* this crap in tons of different formats. gah, those screwed */
+                       /* coders. tho the wavefront obj standard defines exactly two */
+                       /* ways of storing face information. so, i really won't support */
+                       /* such stupid extravaganza here! */
+
+                       picoVec3_t verts  [ 4 ];
+                       picoVec3_t normals[ 4 ];
+                       picoVec2_t coords [ 4 ];
+
+                       int iv [ 4 ], has_v;
+                       int ivt[ 4 ], has_vt = 0;
+                       int ivn[ 4 ], has_vn = 0;
+                       int have_quad = 0;
+                       int slashcount;
+                       int doubleslash;
+                       int i;
+
+                       /* group defs *must* come before faces */
+                       if (curSurface == NULL)
+                               _obj_error_return("No group defined for faces");
+
+#ifdef DEBUG_PM_OBJ_EX
+                       printf("Face: ");
+#endif
+                       /* read vertex/uv/normal indices for the first three face */
+                       /* vertices (cause we only support triangles) into 'i*[]' */
+                       /* store the actual vertex/uv/normal data in three arrays */
+                       /* called 'verts','coords' and 'normals'. */
+                       for (i=0; i<4; i++)
+                       {
+                               char *str;
+
+                               /* get next vertex index string (different */
+                               /* formats are handled below) */
+                               str = _pico_parse( p,0 );
+                               if (str == NULL)
+                               {
+                                       /* just break for quads */
+                                       if (i == 3) break;
+
+                                       /* error otherwise */
+                                       _obj_error_return("Face parse error");
+                               }
+                               /* if this is the fourth index string we're */
+                               /* parsing we assume that we have a quad */
+                               if (i == 3)
+                                       have_quad = 1;
+
+                               /* get slash count once */
+                               if (i == 0)
+                               {
+                                       slashcount  = _pico_strchcount( str,'/' );
+                                       doubleslash =  strstr(str,"//") != NULL;
+                               }
+                               /* handle format 'v//vn' */
+                               if (doubleslash && (slashcount == 2))
+                               {
+                                       has_v = has_vn = 1;
+                                       sscanf( str,"%d//%d",&iv[ i ],&ivn[ i ] );
+                               }
+                               /* handle format 'v/vt/vn' */
+                               else if (!doubleslash && (slashcount == 2))
+                               {
+                                       has_v = has_vt = has_vn = 1;
+                                       sscanf( str,"%d/%d/%d",&iv[ i ],&ivt[ i ],&ivn[ i ] );
+                               }
+                               /* handle format 'v/vt' (non-standard fuckage) */
+                               else if (!doubleslash && (slashcount == 1))
+                               {
+                                       has_v = has_vt = 1;
+                                       sscanf( str,"%d/%d",&iv[ i ],&ivt[ i ] );
+                               }
+                               /* else assume face format 'v' */
+                               /* (must have been invented by some bored granny) */
+                               else {
+                                       /* get single vertex index */
+                                       has_v = 1;
+                                       iv[ i ] = atoi( str );
+
+                                       /* either invalid face format or out of range */
+                                       if (iv[ i ] == 0)
+                                               _obj_error_return("Invalid face format");
+                               }
+                               /* fix useless back references */
+                               /* todo: check if this works as it is supposed to */
+
+                               /* assign new indices */
+                               if (iv [ i ] < 0) iv [ i ] = (numVerts   - iv [ i ]);
+                               if (ivt[ i ] < 0) ivt[ i ] = (numUVs     - ivt[ i ]);
+                               if (ivn[ i ] < 0) ivn[ i ] = (numNormals - ivn[ i ]);
+
+                               /* validate indices */
+                               /* - commented out. index range checks will trigger
+                               if (iv [ i ] < 1) iv [ i ] = 1;
+                               if (ivt[ i ] < 1) ivt[ i ] = 1;
+                               if (ivn[ i ] < 1) ivn[ i ] = 1;
+                               */
+                               /* set vertex origin */
+                               if (has_v)
+                               {
+                                       /* check vertex index range */
+                                       if (iv[ i ] < 1 || iv[ i ] > numVerts)
+                                               _obj_error_return("Vertex index out of range");
+
+                                       /* get vertex data */
+                                       verts[ i ][ 0 ] = vertexData[ iv[ i ] - 1 ].v[ 0 ];
+                                       verts[ i ][ 1 ] = vertexData[ iv[ i ] - 1 ].v[ 1 ];
+                                       verts[ i ][ 2 ] = vertexData[ iv[ i ] - 1 ].v[ 2 ];
+                               }
+                               /* set vertex normal */
+                               if (has_vn)
+                               {
+                                       /* check normal index range */
+                                       if (ivn[ i ] < 1 || ivn[ i ] > numNormals)
+                                               _obj_error_return("Normal index out of range");
+
+                                       /* get normal data */
+                                       normals[ i ][ 0 ] = vertexData[ ivn[ i ] - 1 ].vn[ 0 ];
+                                       normals[ i ][ 1 ] = vertexData[ ivn[ i ] - 1 ].vn[ 1 ];
+                                       normals[ i ][ 2 ] = vertexData[ ivn[ i ] - 1 ].vn[ 2 ];
+                               }
+                               /* set texture coordinate */
+                               if (has_vt)
+                               {
+                                       /* check uv index range */
+                                       if (ivt[ i ] < 1 || ivt[ i ] > numUVs)
+                                               _obj_error_return("UV coord index out of range");
+
+                                       /* get uv coord data */
+                                       coords[ i ][ 0 ] = vertexData[ ivt[ i ] - 1 ].vt[ 0 ];
+                                       coords[ i ][ 1 ] = vertexData[ ivt[ i ] - 1 ].vt[ 1 ];
+                                       coords[ i ][ 1 ] = -coords[ i ][ 1 ];
+                               }
+#ifdef DEBUG_PM_OBJ_EX
+                               printf("(%4d",iv[ i ]);
+                               if (has_vt) printf(" %4d",ivt[ i ]);
+                               if (has_vn) printf(" %4d",ivn[ i ]);
+                               printf(") ");
+#endif
+                       }
+#ifdef DEBUG_PM_OBJ_EX
+                       printf("\n");
+#endif
+                       /* now that we have extracted all the indices and have */
+                       /* read the actual data we need to assign all the crap */
+                       /* to our current pico surface */
+                       if (has_v)
+                       {
+                               int max = 3;
+                               if (have_quad) max = 4;
+
+                               /* assign all surface information */
+                               for (i=0; i<max; i++)
+                               {
+                                       /*if( has_v  )*/ PicoSetSurfaceXYZ       ( curSurface,  (curVertex + i), verts  [ i ] );
+                                       /*if( has_vt )*/ PicoSetSurfaceST        ( curSurface,0,(curVertex + i), coords [ i ] );
+                                       /*if( has_vn )*/ PicoSetSurfaceNormal( curSurface,  (curVertex + i), normals[ i ] );
+                               }
+                               /* add our triangle (A B C) */
+                               PicoSetSurfaceIndex( curSurface,(curFace * 3 + 2),(picoIndex_t)( curVertex + 0 ) );
+                               PicoSetSurfaceIndex( curSurface,(curFace * 3 + 1),(picoIndex_t)( curVertex + 1 ) );
+                               PicoSetSurfaceIndex( curSurface,(curFace * 3 + 0),(picoIndex_t)( curVertex + 2 ) );
+                               curFace++;
+
+                               /* if we don't have a simple triangle, but a quad... */
+                               if (have_quad)
+                               {
+                                       /* we have to add another triangle (2nd half of quad which is A C D) */
+                                       PicoSetSurfaceIndex( curSurface,(curFace * 3 + 2),(picoIndex_t)( curVertex + 0 ) );
+                                       PicoSetSurfaceIndex( curSurface,(curFace * 3 + 1),(picoIndex_t)( curVertex + 2 ) );
+                                       PicoSetSurfaceIndex( curSurface,(curFace * 3 + 0),(picoIndex_t)( curVertex + 3 ) );
+                                       curFace++;
+                               }
+                               /* increase vertex count */
+                               curVertex += max;
+                       }
+               }
+               /* skip unparsed rest of line and continue */
+               _pico_parse_skip_rest( p );
+       }
+       /* free memory used by temporary vertexdata */
+       FreeObjVertexData( vertexData );
+
+       /* return allocated pico model */
+       return model;
+//     return NULL;
+}
+
+/* pico file format module definition */
+const picoModule_t picoModuleOBJ =
+{
+       "0.6-b",                                        /* module version string */
+       "Wavefront ASCII",                      /* module display name */
+       "seaw0lf",                                      /* author's name */
+       "2002 seaw0lf",                         /* module copyright */
+       {
+               "obj",NULL,NULL,NULL    /* default extensions to use */
+       },
+       _obj_canload,                           /* validation routine */
+       _obj_load,                                      /* load routine */
+        NULL,                                          /* save validation routine */
+        NULL                                           /* save routine */
+};