-/* -----------------------------------------------------------------------------\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 */
+};