/* _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; }
/* _ase_load: * loads a 3dsmax ase model file. */ static picoModel_t *_ase_load( PM_PARAMS_LOAD ) { picoModel_t *model; picoParser_t *p; char lastNodeName[ 1024 ]; aseVertex_t* vertices = NULL; aseTexCoord_t* texcoords = NULL; aseColor_t* colors = NULL; aseFace_t* faces = NULL; int numVertices = 0; int numFaces = 0; int numTextureVertices = 0; int numTextureVertexFaces = 0; int numColorVertices = 0; int numColorVertexFaces = 0; int vertexId = 0; int currentVertexFace=0; int currentVertexIndex=0; int counter=0; int submodel=0; aseMaterial_t* materials = NULL; #ifdef DEBUG_PM_ASE clock_t start, finish; double elapsed; start = clock(); #endif /* helper */ #define _ase_error_return(m) \ { \ _pico_printf( PICO_ERROR,"%s in ASE, line %d.",m,p->curLine); \ _pico_free_parser( p ); \ PicoFreeModel( model ); \ return NULL; \ } /* create 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 ); /* initialize some stuff */ memset( lastNodeName,0,sizeof(lastNodeName) ); /* parse ase model file */ while( 1 ) { /* get first token on line */ if (_pico_parse_first( p ) == NULL) break; /* we just skip empty lines */ if (p->token == NULL || !strlen( p->token )) continue; /* we skip invalid ase statements */ if (p->token[0] != '*' && p->token[0] != '{' && p->token[0] != '}') { _pico_parse_skip_rest( p ); continue; } /* remember node name */ if (!_pico_stricmp(p->token,"*node_name")) { /* read node name */ char *ptr = _pico_parse( p,0 ); if (ptr == NULL) _ase_error_return("Node name parse error"); /* remember node name */ strncpy( lastNodeName,ptr,sizeof(lastNodeName) ); } /* model mesh (originally contained within geomobject) */ else if (!_pico_stricmp(p->token,"*mesh")) { /* finish existing surface */ _ase_submit_triangles(model, materials, vertices, texcoords, colors, faces, numFaces,numVertices,submodel++); _pico_free(faces); _pico_free(vertices); _pico_free(texcoords); _pico_free(colors); } else if (!_pico_stricmp(p->token,"*mesh_numvertex")) { if (!_pico_parse_int( p, &numVertices) ) _ase_error_return("Missing MESH_NUMVERTEX value"); vertices = _pico_calloc(numVertices, sizeof(aseVertex_t)); currentVertexIndex=0; } else if (!_pico_stricmp(p->token,"*mesh_numfaces")) { if (!_pico_parse_int( p, &numFaces) ) _ase_error_return("Missing MESH_NUMFACES value"); faces = _pico_calloc(numFaces, sizeof(aseFace_t)); } else if (!_pico_stricmp(p->token,"*mesh_numtvertex")) { if (!_pico_parse_int( p, &numTextureVertices) ) _ase_error_return("Missing MESH_NUMTVERTEX value"); texcoords = _pico_calloc(numTextureVertices, sizeof(aseTexCoord_t)); } else if (!_pico_stricmp(p->token,"*mesh_numtvfaces")) { if (!_pico_parse_int( p, &numTextureVertexFaces) ) _ase_error_return("Missing MESH_NUMTVFACES value"); } else if (!_pico_stricmp(p->token,"*mesh_numcvertex")) { if (!_pico_parse_int( p, &numColorVertices) ) _ase_error_return("Missing MESH_NUMCVERTEX value"); colors = _pico_calloc(numColorVertices, sizeof(aseColor_t)); memset( colors, 255, numColorVertices * sizeof( aseColor_t ) ); /* ydnar: force colors to white initially */ } else if (!_pico_stricmp(p->token,"*mesh_numcvfaces")) { if (!_pico_parse_int( p, &numColorVertexFaces) ) _ase_error_return("Missing MESH_NUMCVFACES value"); } /* mesh material reference. this usually comes at the end of */ /* geomobjects after the mesh blocks. we must assume that the */ /* new mesh was already created so all we can do here is assign */ /* the material reference id (shader index) now. */ else if (!_pico_stricmp(p->token,"*material_ref")) { int mtlId; /* get the material ref (0..n) */ if (!_pico_parse_int( p,&mtlId) ) _ase_error_return("Missing material reference ID"); { int i = 0; /* fix up all of the aseFaceList in the surface to point to the parent material */ /* we've already saved off their subMtl */ for(; i < numFaces; ++i) { faces[i].materialId = mtlId; } } } /* model mesh vertex */ else if (!_pico_stricmp(p->token,"*mesh_vertex")) { int index; if( numVertices == 0 ) _ase_error_return("Vertex parse error"); /* get vertex data (orig: index +y -x +z) */ if (!_pico_parse_int( p,&index )) _ase_error_return("Vertex parse error"); if (!_pico_parse_vec( p,vertices[index].xyz )) _ase_error_return("Vertex parse error"); vertices[index].id = vertexId++; } else if (!_pico_stricmp(p->token,"*mesh_facenormal")) { //Grab the faceindex for the next vertex normals. if( numVertices == 0 ) _ase_error_return("Vertex parse error (facenormals)"); if (!_pico_parse_int( p,¤tVertexFace )) _ase_error_return("Vertex parse error"); if (!_pico_parse_vec( p,faces[currentVertexFace].facenormal )) _ase_error_return("Vertex parse error"); } /* model mesh vertex normal */ else if (!_pico_stricmp(p->token,"*mesh_vertexnormal")) { int index; if( numVertices == 0 ) _ase_error_return("Vertex parse error"); /* get vertex data (orig: index +y -x +z) */ if (!_pico_parse_int( p,&index )) _ase_error_return("Vertex parse error"); //^^ Index is 'wrong' in .ase models. they reference the same vert index with multiple normals.. // I've tried, this is a lost cause. Use the SG's // /* if (!_pico_parse_vec( p,vertices[counter].normal )) _ase_error_return("Vertex parse error"); vertices[counter].faceid=index; counter++; */ } /* model mesh face */ else if (!_pico_stricmp(p->token,"*mesh_normals")) { // counter=0; //part of the above vertex normals fix } /* model mesh face */ else if (!_pico_stricmp(p->token,"*mesh_face")) { picoIndex_t indexes[3]; int index; if( numFaces == 0 ) _ase_error_return("Face parse error"); /* get face index */ if (!_pico_parse_int( p,&index )) _ase_error_return("Face parse error"); /* get 1st vertex index */ _pico_parse( p,0 ); if (!_pico_parse_int( p,&indexes[0] )) _ase_error_return("Face parse error"); /* get 2nd vertex index */ _pico_parse( p,0 ); if (!_pico_parse_int( p,&indexes[1] )) _ase_error_return("Face parse error"); /* get 3rd vertex index */ _pico_parse( p,0 ); if (!_pico_parse_int( p,&indexes[2] )) _ase_error_return("Face parse error"); /* parse to the subMaterial ID */ while ( 1 ) { if (!_pico_parse (p,0)) /* EOL */ { break; } if (!_pico_stricmp (p->token,"*MESH_SMOOTHING" )) { int total=0; char* point; char* start; _pico_parse(p,0); point=p->token; start=point; faces[index].smoothingGroup=0; //Super dodgy comma delimited string parse while (*point<'A') { if (*point<=32 || *point==',') { total=atoi(start); if (total!=0) { faces[index].smoothingGroup+=1<<total; } start=point+1; } point++; } } if (!_pico_stricmp (p->token,"*MESH_MTLID" )) { _pico_parse_int ( p , &faces[index].subMaterialId ); } } faces[index].materialId = 0; faces[index].indices[0] = indexes[2]; faces[index].indices[1] = indexes[1]; faces[index].indices[2] = indexes[0]; } /* model texture vertex */ else if (!_pico_stricmp(p->token,"*mesh_tvert")) { int index; if( numVertices == 0 ) _ase_error_return("Vertex parse error"); /* get uv vertex index */ if (!_pico_parse_int( p,&index )) _ase_error_return("UV vertex parse error"); /* get uv vertex s */ if (!_pico_parse_float( p,&texcoords[index].texcoord[0] )) _ase_error_return("UV vertex parse error"); /* get uv vertex t */ if (!_pico_parse_float( p,&texcoords[index].texcoord[1] )) _ase_error_return("UV vertex parse error"); /* ydnar: invert t */ texcoords[index].texcoord[ 1 ] = 1.0f - texcoords[index].texcoord[ 1 ]; } /* ydnar: model mesh texture face */ else if( !_pico_stricmp( p->token, "*mesh_tface" ) ) { picoIndex_t indexes[3]; int index; if( numFaces == 0 ) _ase_error_return("Texture face parse error"); /* get face index */ if (!_pico_parse_int( p,&index )) _ase_error_return("Texture face parse error"); /* get 1st vertex index */ if (!_pico_parse_int( p,&indexes[0] )) _ase_error_return("Texture face parse error"); /* get 2nd vertex index */ if (!_pico_parse_int( p,&indexes[1] )) _ase_error_return("Texture face parse error"); /* get 3rd vertex index */ if (!_pico_parse_int( p,&indexes[2] )) _ase_error_return("Texture face parse error"); faces[index].indices[3] = indexes[2]; faces[index].indices[4] = indexes[1]; faces[index].indices[5] = indexes[0]; } /* model color vertex */ else if (!_pico_stricmp(p->token,"*mesh_vertcol")) { int index; float colorInput; if( numVertices == 0 ) _ase_error_return("Color Vertex parse error"); /* get color vertex index */ if (!_pico_parse_int( p,&index )) _ase_error_return("Color vertex parse error"); /* get R component */ if (!_pico_parse_float( p,&colorInput )) _ase_error_return("Color vertex parse error"); colors[index].color[0] = (picoByte_t)(colorInput * 255); /* get G component */ if (!_pico_parse_float( p,&colorInput )) _ase_error_return("Color vertex parse error"); colors[index].color[1] = (picoByte_t)(colorInput * 255); /* get B component */ if (!_pico_parse_float( p,&colorInput )) _ase_error_return("Color vertex parse error"); colors[index].color[2] = (picoByte_t)(colorInput * 255); /* leave alpha alone since we don't get any data from the ASE format */ colors[index].color[3] = 255; /* 27 hack, red as alpha */ colors[index].color[3]=colors[index].color[0]; colors[index].color[0]=255; colors[index].color[1]=255; colors[index].color[2]=255; } /* model color face */ else if (!_pico_stricmp(p->token,"*mesh_cface")) { picoIndex_t indexes[3]; int index; if( numFaces == 0 ) _ase_error_return("Face parse error"); /* get face index */ if (!_pico_parse_int( p,&index )) _ase_error_return("Face parse error"); /* get 1st cvertex index */ // _pico_parse( p,0 ); if (!_pico_parse_int( p,&indexes[0] )) _ase_error_return("Face parse error"); /* get 2nd cvertex index */ // _pico_parse( p,0 ); if (!_pico_parse_int( p,&indexes[1] )) _ase_error_return("Face parse error"); /* get 3rd cvertex index */ // _pico_parse( p,0 ); if (!_pico_parse_int( p,&indexes[2] )) _ase_error_return("Face parse error"); faces[index].indices[6] = indexes[2]; faces[index].indices[7] = indexes[1]; faces[index].indices[8] = indexes[0]; } /* model material */ else if( !_pico_stricmp( p->token, "*material" ) ) { aseSubMaterial_t* subMaterial = NULL; picoShader_t *shader; int level = 1, index; char materialName[ 1024 ]; float transValue = 0.0f, shineValue = 1.0f; picoColor_t ambientColor, diffuseColor, specularColor; char *mapname = NULL; int subMtlId, subMaterialLevel = -1; /* get material index */ _pico_parse_int( p,&index ); /* check brace */ if (!_pico_parse_check(p,1,"{")) _ase_error_return("Material missing opening brace"); /* parse material block */ while( 1 ) { /* get next token */ if (_pico_parse(p,1) == NULL) break; if (!strlen(p->token)) continue; /* handle levels */ if (p->token[0] == '{') level++; if (p->token[0] == '}') level--; if (!level) break; if( level == subMaterialLevel ) { /* set material name */ _pico_first_token( materialName ); PicoSetShaderName( shader, materialName); /* set shader's transparency */ PicoSetShaderTransparency( shader,transValue ); /* set shader's ambient color */ PicoSetShaderAmbientColor( shader,ambientColor ); /* set diffuse alpha to transparency */ diffuseColor[3] = (picoByte_t)( transValue * 255.0 ); /* set shader's diffuse color */ PicoSetShaderDiffuseColor( shader,diffuseColor ); /* set shader's specular color */ PicoSetShaderSpecularColor( shader,specularColor ); /* set shader's shininess */ PicoSetShaderShininess( shader,shineValue ); /* set material map name */ PicoSetShaderMapName( shader, mapname ); subMaterial = _ase_add_submaterial( &materials, index, subMtlId, shader ); subMaterialLevel = -1; } /* parse submaterial index */ if (!_pico_stricmp(p->token,"*submaterial")) { /* allocate new pico shader */ _pico_parse_int( p , &subMtlId ); shader = PicoNewShader( model ); if (shader == NULL) { PicoFreeModel( model ); return NULL; } subMaterialLevel = level; } /* parse material name */ else if (!_pico_stricmp(p->token,"*material_name")) { char* name = _pico_parse(p,0); if ( name == NULL) _ase_error_return("Missing material name"); strcpy ( materialName , name ); /* skip rest and continue with next token */ _pico_parse_skip_rest( p ); continue; } /* parse material transparency */ else if (!_pico_stricmp(p->token,"*material_transparency")) { /* get transparency value from ase */ if (!_pico_parse_float( p,&transValue )) _ase_error_return("Material transparency parse error"); /* skip rest and continue with next token */ _pico_parse_skip_rest( p ); continue; } /* parse material shininess */ else if (!_pico_stricmp(p->token,"*material_shine")) { /* remark: * - not sure but instead of '*material_shine' i might * need to use '*material_shinestrength' */ /* get shine value from ase */ if (!_pico_parse_float( p,&shineValue )) _ase_error_return("Material shine parse error"); /* scale ase shine range 0..1 to pico range 0..127 */ shineValue *= 128.0; /* skip rest and continue with next token */ _pico_parse_skip_rest( p ); continue; } /* parse ambient material color */ else if (!_pico_stricmp(p->token,"*material_ambient")) { picoVec3_t vec; /* get r,g,b float values from ase */ if (!_pico_parse_vec( p,vec )) _ase_error_return("Material color parse error"); /* setup 0..255 range color values */ ambientColor[ 0 ] = (int)( vec[ 0 ] * 255.0 ); ambientColor[ 1 ] = (int)( vec[ 1 ] * 255.0 ); ambientColor[ 2 ] = (int)( vec[ 2 ] * 255.0 ); ambientColor[ 3 ] = (int)( 255 ); /* skip rest and continue with next token */ _pico_parse_skip_rest( p ); continue; } /* parse diffuse material color */ else if (!_pico_stricmp(p->token,"*material_diffuse")) { picoVec3_t vec; /* get r,g,b float values from ase */ if (!_pico_parse_vec( p,vec )) _ase_error_return("Material color parse error"); /* setup 0..255 range color */ diffuseColor[ 0 ] = (int)( vec[ 0 ] * 255.0 ); diffuseColor[ 1 ] = (int)( vec[ 1 ] * 255.0 ); diffuseColor[ 2 ] = (int)( vec[ 2 ] * 255.0 ); diffuseColor[ 3 ] = (int)( 255 ); /* skip rest and continue with next token */ _pico_parse_skip_rest( p ); continue; } /* parse specular material color */ else if (!_pico_stricmp(p->token,"*material_specular")) { picoVec3_t vec; /* get r,g,b float values from ase */ if (!_pico_parse_vec( p,vec )) _ase_error_return("Material color parse error"); /* setup 0..255 range color */ specularColor[ 0 ] = (int)( vec[ 0 ] * 255 ); specularColor[ 1 ] = (int)( vec[ 1 ] * 255 ); specularColor[ 2 ] = (int)( vec[ 2 ] * 255 ); specularColor[ 3 ] = (int)( 255 ); /* skip rest and continue with next token */ _pico_parse_skip_rest( p ); continue; } /* material diffuse map */ else if (!_pico_stricmp(p->token,"*map_diffuse") ) { int sublevel = 0; /* parse material block */ while( 1 ) { /* get next token */ if (_pico_parse(p,1) == NULL) break; if (!strlen(p->token)) continue; /* handle levels */ if (p->token[0] == '{') sublevel++; if (p->token[0] == '}') sublevel--; if (!sublevel) break; /* parse diffuse map bitmap */ if (!_pico_stricmp(p->token,"*bitmap")) { char* name = _pico_parse(p,0); if (name == NULL) _ase_error_return("Missing material map bitmap name"); mapname = _pico_alloc ( strlen ( name ) + 1 ); strcpy ( mapname, name ); /* skip rest and continue with next token */ _pico_parse_skip_rest( p ); continue; } } } /* end map_diffuse block */ } /* end material block */ if( subMaterial == NULL ) { /* allocate new pico shader */ shader = PicoNewShader( model ); if (shader == NULL) { PicoFreeModel( model ); return NULL; } /* set material name */ PicoSetShaderName( shader,materialName ); /* set shader's transparency */ PicoSetShaderTransparency( shader,transValue ); /* set shader's ambient color */ PicoSetShaderAmbientColor( shader,ambientColor ); /* set diffuse alpha to transparency */ diffuseColor[3] = (picoByte_t)( transValue * 255.0 ); /* set shader's diffuse color */ PicoSetShaderDiffuseColor( shader,diffuseColor ); /* set shader's specular color */ PicoSetShaderSpecularColor( shader,specularColor ); /* set shader's shininess */ PicoSetShaderShininess( shader,shineValue ); /* set material map name */ PicoSetShaderMapName( shader, mapname ); /* extract shadername from bitmap path */ if(mapname != NULL) { char* p = mapname; /* convert to shader-name format */ { /* unix-style path separators */ char* s = mapname; for(; *s != '\0'; ++s) { if(*s == '\\') { *s = '/'; } } } { /* remove extension */ char* last_period = strrchr(p, '.'); if(last_period != NULL) { *last_period = '\0'; } } /* find game root */ for(; *p != '\0'; ++p) { if(_pico_strnicmp(p, "quake", 5) == 0 || _pico_strnicmp(p, "doom", 4) == 0) { break; } } /* root-relative */ for(; *p != '\0'; ++p) { if(*p == '/') { ++p; break; } } /* game-relative */ for(; *p != '\0'; ++p) { if(*p == '/') { ++p; break; } } if(*p != '\0') { /* set material name */ PicoSetShaderName( shader,p ); } } /* this is just a material with 1 submaterial */ subMaterial = _ase_add_submaterial( &materials, index, 0, shader ); } /* ydnar: free mapname */ if( mapname != NULL ) _pico_free( mapname ); } // !_pico_stricmp ( "*material" ) /* skip unparsed rest of line and continue */ _pico_parse_skip_rest( p ); } /* ydnar: finish existing surface */ _ase_submit_triangles(model, materials, vertices, texcoords, colors, faces, numFaces,numVertices,submodel++); _pico_free(faces); _pico_free(vertices); _pico_free(texcoords); _pico_free(colors); #ifdef DEBUG_PM_ASE _ase_print_materials(materials); finish = clock(); elapsed = (double)(finish - start) / CLOCKS_PER_SEC; _pico_printf( PICO_NORMAL, "Loaded model in in %-.2f second(s)\n", elapsed ); #endif //DEBUG_PM_ASE _ase_free_materials(&materials); _pico_free_parser( p ); /* return allocated pico model */ return model; }
static picoModel_t *_terrain_load( PM_PARAMS_LOAD ){ int i, j, v, pw[ 5 ], r; picoParser_t *p; char *shader, *heightmapFile, *colormapFile; picoVec3_t scale, origin; unsigned char *imageBuffer; int imageBufSize, w, h, cw, ch; unsigned char *heightmap, *colormap, *heightPixel, *colorPixel; picoModel_t *picoModel; picoSurface_t *picoSurface; picoShader_t *picoShader; picoVec3_t xyz, normal; picoVec2_t st; picoColor_t color; /* create pico parser */ p = _pico_new_parser( (const picoByte_t*) buffer, bufSize ); if ( p == NULL ) { return NULL; } /* get first token */ if ( _pico_parse_first( p ) == NULL ) { return NULL; } /* check first token */ if ( _pico_stricmp( p->token, "picoterrain" ) ) { _pico_printf( PICO_ERROR, "Invalid PicoTerrain model" ); _pico_free_parser( p ); return NULL; } /* setup */ shader = heightmapFile = colormapFile = NULL; _pico_set_vec( scale, 512, 512, 32 ); /* parse ase model file */ while ( 1 ) { /* get first token on line */ if ( !_pico_parse_first( p ) ) { break; } /* skip empty lines */ if ( !p->token || !p->token[ 0 ] ) { continue; } /* shader */ if ( !_pico_stricmp( p->token, "shader" ) ) { if ( _pico_parse( p, 0 ) && p->token[ 0 ] ) { if ( shader != NULL ) { _pico_free( shader ); } shader = _pico_clone_alloc( p->token ); } } /* heightmap */ else if ( !_pico_stricmp( p->token, "heightmap" ) ) { if ( _pico_parse( p, 0 ) && p->token[ 0 ] ) { if ( heightmapFile != NULL ) { _pico_free( heightmapFile ); } heightmapFile = _pico_clone_alloc( p->token ); } } /* colormap */ else if ( !_pico_stricmp( p->token, "colormap" ) ) { if ( _pico_parse( p, 0 ) && p->token[ 0 ] ) { if ( colormapFile != NULL ) { _pico_free( colormapFile ); } colormapFile = _pico_clone_alloc( p->token ); } } /* scale */ else if ( !_pico_stricmp( p->token, "scale" ) ) { _pico_parse_vec( p, scale ); } /* skip unparsed rest of line and continue */ _pico_parse_skip_rest( p ); } /* ----------------------------------------------------------------- */ /* load heightmap */ heightmap = imageBuffer = NULL; _pico_load_file( heightmapFile, &imageBuffer, &imageBufSize ); _terrain_load_tga_buffer( imageBuffer, &heightmap, &w, &h ); _pico_free( heightmapFile ); _pico_free_file( imageBuffer ); if ( heightmap == NULL || w < 2 || h < 2 ) { _pico_printf( PICO_ERROR, "PicoTerrain model with invalid heightmap" ); if ( shader != NULL ) { _pico_free( shader ); } if ( colormapFile != NULL ) { _pico_free( colormapFile ); } _pico_free_parser( p ); return NULL; } /* set origin (bottom lowest corner of terrain mesh) */ _pico_set_vec( origin, ( w / -2 ) * scale[ 0 ], ( h / -2 ) * scale[ 1 ], -128 * scale[ 2 ] ); /* load colormap */ colormap = imageBuffer = NULL; _pico_load_file( colormapFile, &imageBuffer, &imageBufSize ); _terrain_load_tga_buffer( imageBuffer, &colormap, &cw, &ch ); _pico_free( colormapFile ); _pico_free_file( imageBuffer ); if ( cw != w || ch != h ) { _pico_printf( PICO_WARNING, "PicoTerrain colormap/heightmap size mismatch" ); _pico_free( colormap ); colormap = NULL; } /* ----------------------------------------------------------------- */ /* create new pico model */ picoModel = PicoNewModel(); if ( picoModel == NULL ) { _pico_printf( PICO_ERROR, "Unable to allocate a new model" ); return NULL; } /* do model setup */ PicoSetModelFrameNum( picoModel, frameNum ); PicoSetModelNumFrames( picoModel, 1 ); /* sea */ PicoSetModelName( picoModel, fileName ); PicoSetModelFileName( picoModel, fileName ); /* allocate new pico surface */ picoSurface = PicoNewSurface( picoModel ); if ( picoSurface == NULL ) { _pico_printf( PICO_ERROR, "Unable to allocate a new model surface" ); PicoFreeModel( picoModel ); /* sea */ return NULL; } /* terrain surfaces are triangle meshes */ PicoSetSurfaceType( picoSurface, PICO_TRIANGLES ); /* set surface name */ PicoSetSurfaceName( picoSurface, "picoterrain" ); /* create new pico shader */ picoShader = PicoNewShader( picoModel ); if ( picoShader == NULL ) { _pico_printf( PICO_ERROR, "Unable to allocate a new model shader" ); PicoFreeModel( picoModel ); _pico_free( shader ); return NULL; } /* detox and set shader name */ _pico_setfext( shader, "" ); _pico_unixify( shader ); PicoSetShaderName( picoShader, shader ); _pico_free( shader ); /* associate current surface with newly created shader */ PicoSetSurfaceShader( picoSurface, picoShader ); /* make bogus normal */ _pico_set_vec( normal, 0.0f, 0.0f, 0.0f ); /* create mesh */ for ( j = 0; j < h; j++ ) { for ( i = 0; i < w; i++ ) { /* get pointers */ v = i + ( j * w ); heightPixel = heightmap + v * 4; colorPixel = colormap ? colormap + v * 4 : NULL; /* set xyz */ _pico_set_vec( xyz, origin[ 0 ] + scale[ 0 ] * i, origin[ 1 ] + scale[ 1 ] * j, origin[ 2 ] + scale[ 2 ] * heightPixel[ 0 ] ); PicoSetSurfaceXYZ( picoSurface, v, xyz ); /* set normal */ PicoSetSurfaceNormal( picoSurface, v, normal ); /* set st */ st[ 0 ] = (float) i; st[ 1 ] = (float) j; PicoSetSurfaceST( picoSurface, 0, v, st ); /* set color */ if ( colorPixel != NULL ) { _pico_set_color( color, colorPixel[ 0 ], colorPixel[ 1 ], colorPixel[ 2 ], colorPixel[ 3 ] ); } else{ _pico_set_color( color, 255, 255, 255, 255 ); } PicoSetSurfaceColor( picoSurface, 0, v, color ); /* set triangles (zero alpha in heightmap suppresses this quad) */ if ( i < ( w - 1 ) && j < ( h - 1 ) && heightPixel[ 3 ] >= 128 ) { /* set indexes */ pw[ 0 ] = i + ( j * w ); pw[ 1 ] = i + ( ( j + 1 ) * w ); pw[ 2 ] = i + 1 + ( ( j + 1 ) * w ); pw[ 3 ] = i + 1 + ( j * w ); pw[ 4 ] = i + ( j * w ); /* same as pw[ 0 ] */ /* set radix */ r = ( i + j ) & 1; /* make first triangle */ PicoSetSurfaceIndex( picoSurface, ( v * 6 + 0 ), (picoIndex_t) pw[ r + 0 ] ); PicoSetSurfaceIndex( picoSurface, ( v * 6 + 1 ), (picoIndex_t) pw[ r + 1 ] ); PicoSetSurfaceIndex( picoSurface, ( v * 6 + 2 ), (picoIndex_t) pw[ r + 2 ] ); /* make second triangle */ PicoSetSurfaceIndex( picoSurface, ( v * 6 + 3 ), (picoIndex_t) pw[ r + 0 ] ); PicoSetSurfaceIndex( picoSurface, ( v * 6 + 4 ), (picoIndex_t) pw[ r + 2 ] ); PicoSetSurfaceIndex( picoSurface, ( v * 6 + 5 ), (picoIndex_t) pw[ r + 3 ] ); } } } /* free stuff */ _pico_free_parser( p ); _pico_free( heightmap ); _pico_free( colormap ); /* return the new pico model */ return picoModel; }
/** * @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; }
static int _obj_mtl_load (picoModel_t *model) { 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 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 = 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; }