X-Git-Url: http://git.xonotic.org/?a=blobdiff_plain;f=libs%2Fpicomodel%2Fpm_obj.c;h=6cb406682759b8a1372a7c700406606ae7f97a67;hb=99980506540d9546dad31223a6eadf126ba68121;hp=2dc27e1dd5876a87d5d6e7a9e5eceadd82272265;hpb=80378101101ca1762bbf5638a9e3566893096d8a;p=xonotic%2Fnetradiant.git diff --git a/libs/picomodel/pm_obj.c b/libs/picomodel/pm_obj.c index 2dc27e1d..6cb40668 100644 --- a/libs/picomodel/pm_obj.c +++ b/libs/picomodel/pm_obj.c @@ -1,858 +1,858 @@ -/* ----------------------------------------------------------------------------- - -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 .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.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