/** * @brief 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; picoShader_t *shader; /* 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 */ shader = _obj_default_shader(model); #if 0 shader = _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); /* associate current surface with newly created shader */ PicoSetSurfaceShader(newSurface, shader); #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++) { PicoSetSurfaceXYZ(curSurface, (curVertex + i), verts[i]); PicoSetSurfaceST(curSurface, 0, (curVertex + i), coords[i]); 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++; } /* associate current surface with newly created shader */ PicoSetSurfaceShader(curSurface, shader); /* 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; }
/* _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 = 0; 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; \ curVertex = 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; \ } /* allocate 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 */ _obj_mtl_load( model ); /* 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! */ const int numPointsMax = 128; picoVec3_t verts [ numPointsMax ]; picoVec3_t normals[ numPointsMax ]; picoVec2_t coords [ numPointsMax ]; int iv [ numPointsMax ], has_v; int ivt[ numPointsMax ], has_vt = 0; int ivn[ numPointsMax ], has_vn = 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 < numPointsMax; i++ ) { char *str; /* get next vertex index string (different */ /* formats are handled below) */ str = _pico_parse( p,0 ); if ( str == NULL ) { /* got nuff points */ if ( i >= 3 ) { break; } /* error otherwise */ _obj_error_return( "Face parse error" ); } /* 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 */ /* assign new indices */ if ( iv [ i ] < 0 ) { iv [ i ] = ( numVerts + iv [ i ] + 1 ); } if ( ivt[ i ] < 0 ) { ivt[ i ] = ( numUVs + ivt[ i ] + 1 ); } if ( ivn[ i ] < 0 ) { ivn[ i ] = ( numNormals + ivn[ i ] + 1 ); } /* 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[ 2 ]; verts[ i ][ 2 ] = vertexData[ iv[ i ] - 1 ].v[ 1 ]; } /* 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[ 2 ]; normals[ i ][ 2 ] = vertexData[ ivn[ i ] - 1 ].vn[ 1 ]; } /* 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 ]; } #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 ) { const int numPoints = i; /* assign all surface information */ for ( i = 0; i < numPoints; 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 ] ); if( curSurface && curSurface->shader ) PicoSetSurfaceColor( curSurface, 0, ( curVertex + i ), curSurface->shader->diffuseColor ); } /* add triangles */ for ( i = 1; i < numPoints - 1; ++i ) { PicoSetSurfaceIndex( curSurface,( curFace * 3 + 2 ),(picoIndex_t)( curVertex + 0 ) ); PicoSetSurfaceIndex( curSurface,( curFace * 3 + 1 ),(picoIndex_t)( curVertex + i ) ); PicoSetSurfaceIndex( curSurface,( curFace * 3 + 0 ),(picoIndex_t)( curVertex + i + 1 ) ); curFace++; } /* increase vertex count */ curVertex += numPoints; } } 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 \"%s\" in OBJ, line %d. Making a default shader.", name, 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; }