-/* -----------------------------------------------------------------------------\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( (const 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 );
+ 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 1
+
+ /* 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;
+ picoShader_t *shader;
+
+ /* 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;
+ }
+ /* create a new pico shader */
+ shader = PicoNewShader( model );
+ if ( shader == NULL ) {
+ _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;
+
+ int autoGroupNumber = 0;
+ char autoGroupNameBuf[64];
+
+#define AUTO_GROUPNAME( namebuf ) \
+ sprintf( namebuf, "__autogroup_%d", autoGroupNumber++ )
+#define NEW_SURFACE( name ) \
+ { \
+ picoSurface_t *newSurface; \
+ /* allocate a pico surface */ \
+ newSurface = PicoNewSurface( model ); \
+ if ( newSurface == NULL ) { \
+ _obj_error_return( "Error allocating surface" ); } \
+ /* reset face index for surface */ \
+ curFace = 0; \
+ /* if we can, assign the previous shader to this surface */ \
+ if ( curSurface ) { \
+ PicoSetSurfaceShader( newSurface, curSurface->shader ); } \
+ /* set ptr to current surface */ \
+ curSurface = newSurface; \
+ /* we use triangle meshes */ \
+ PicoSetSurfaceType( newSurface,PICO_TRIANGLES ); \
+ /* set surface name */ \
+ PicoSetSurfaceName( newSurface,name ); \
+ }
+
+ /* 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( (const 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 1
+ _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" ) ) {
+ 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
+ }
+
+ if ( curFace == 0 && curSurface != NULL ) {
+ PicoSetSurfaceName( curSurface,groupName );
+ }
+ else
+ {
+ NEW_SURFACE( 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 = 0;
+ int doubleslash = 0;
+ int i;
+
+ if ( curSurface == NULL ) {
+ _pico_printf( PICO_WARNING,"No group defined for faces, so creating an autoSurface in OBJ, line %d.",p->curLine );
+ AUTO_GROUPNAME( autoGroupNameBuf );
+ NEW_SURFACE( autoGroupNameBuf );
+ }
+
+ /* 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;
+ }
+ }
+ else if ( !_pico_stricmp( p->token,"usemtl" ) ) {
+ picoShader_t *shader;
+ char *name;
+
+ /* get material name */
+ name = _pico_parse( p,0 );
+
+ if ( curFace != 0 || curSurface == NULL ) {
+ _pico_printf( PICO_WARNING,"No group defined for usemtl, so creating an autoSurface in OBJ, line %d.",p->curLine );
+ AUTO_GROUPNAME( autoGroupNameBuf );
+ NEW_SURFACE( autoGroupNameBuf );
+ }
+
+ /* validate material name */
+ if ( name == NULL || !strlen( name ) ) {
+ _pico_printf( PICO_ERROR,"Missing material name in OBJ, line %d.",p->curLine );
+ }
+ else
+ {
+ shader = PicoFindShader( model, name, 1 );
+ if ( shader == NULL ) {
+ _pico_printf( PICO_WARNING,"Undefined material name in OBJ, line %d. Making a default shader.",p->curLine );
+
+ /* create a new pico shader */
+ shader = PicoNewShader( model );
+ if ( shader != NULL ) {
+ PicoSetShaderName( shader,name );
+ PicoSetShaderMapName( shader,name );
+ PicoSetSurfaceShader( curSurface, shader );
+ }
+ }
+ else
+ {
+ PicoSetSurfaceShader( curSurface, shader );
+ }
+ }
+ }
+ /* 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 */
+};