1 /* -----------------------------------------------------------------------------
5 Copyright (c) 2002, Randy Reddig & seaw0lf
8 Redistribution and use in source and binary forms, with or without modification,
9 are permitted provided that the following conditions are met:
11 Redistributions of source code must retain the above copyright notice, this list
12 of conditions and the following disclaimer.
14 Redistributions in binary form must reproduce the above copyright notice, this
15 list of conditions and the following disclaimer in the documentation and/or
16 other materials provided with the distribution.
18 Neither the names of the copyright holders nor the names of its contributors may
19 be used to endorse or promote products derived from this software without
20 specific prior written permission.
22 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
23 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
24 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
25 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
26 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
27 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
29 ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
31 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33 ----------------------------------------------------------------------------- */
41 #include "picointernal.h"
43 /* disable warnings */
45 #pragma warning( disable:4100 ) /* unref param */
49 * - '_obj_load' code crashes in a weird way after
50 * '_obj_mtl_load' for a few .mtl files
51 * - process 'mtllib' rather than using <model>.mtl
52 * - handle 'usemtl' statements
54 /* uncomment when debugging this module */
55 /* #define DEBUG_PM_OBJ */
56 /* #define DEBUG_PM_OBJ_EX */
58 /* this holds temporary vertex data read by parser */
59 typedef struct SObjVertexData
61 picoVec3_t v; /* geometric vertices */
62 picoVec2_t vt; /* texture vertices */
63 picoVec3_t vn; /* vertex normals (optional) */
68 * validates a wavefront obj model file.
70 static int _obj_canload( PM_PARAMS_CANLOAD )
74 /* check data length */
76 return PICO_PMV_ERROR_SIZE;
78 /* first check file extension. we have to do this for objs */
79 /* cause there is no good way to identify the contents */
80 if (_pico_stristr(fileName,".obj") != NULL ||
81 _pico_stristr(fileName,".wf" ) != NULL)
85 /* if the extension check failed we parse through the first */
86 /* few lines in file and look for common keywords often */
87 /* appearing at the beginning of wavefront objects */
89 /* alllocate a new pico parser */
90 p = _pico_new_parser( (picoByte_t *)buffer,bufSize );
92 return PICO_PMV_ERROR_MEMORY;
94 /* parse obj head line by line for type check */
97 /* get first token on line */
98 if (_pico_parse_first( p ) == NULL)
101 /* we only parse the first few lines, say 80 */
105 /* skip empty lines */
106 if (p->token == NULL || !strlen( p->token ))
109 /* material library keywords are teh good */
110 if (!_pico_stricmp(p->token,"usemtl") ||
111 !_pico_stricmp(p->token,"mtllib") ||
112 !_pico_stricmp(p->token,"g") ||
113 !_pico_stricmp(p->token,"v")) /* v,g bit fishy, but uh... */
115 /* free the pico parser thing */
116 _pico_free_parser( p );
118 /* seems to be a valid wavefront obj */
121 /* skip rest of line */
122 _pico_parse_skip_rest( p );
124 /* free the pico parser thing */
125 _pico_free_parser( p );
127 /* doesn't really look like an obj to us */
128 return PICO_PMV_ERROR;
131 /* SizeObjVertexData:
132 * This pretty piece of 'alloc ahead' code dynamically
133 * allocates - and reallocates as soon as required -
134 * my vertex data array in even steps.
136 #define SIZE_OBJ_STEP 4096
138 static TObjVertexData *SizeObjVertexData(
139 TObjVertexData *vertexData, int reqEntries,
140 int *entries, int *allocated)
147 if (entries == NULL || allocated == NULL)
148 return NULL; /* must have */
150 /* no need to grow yet */
151 if (vertexData && (reqEntries < *allocated))
153 *entries = reqEntries;
156 /* given vertex data ptr not allocated yet */
157 if (vertexData == NULL)
159 /* how many entries to allocate */
160 newAllocated = (reqEntries > SIZE_OBJ_STEP) ?
161 reqEntries : SIZE_OBJ_STEP;
163 /* throw out an extended debug message */
164 #ifdef DEBUG_PM_OBJ_EX
165 printf("SizeObjVertexData: allocate (%d entries)\n",
168 /* first time allocation */
169 vertexData = (TObjVertexData *)
170 _pico_alloc( sizeof(TObjVertexData) * newAllocated );
172 /* allocation failed */
173 if (vertexData == NULL)
176 /* allocation succeeded */
177 *allocated = newAllocated;
178 *entries = reqEntries;
181 /* given vertex data ptr needs to be resized */
182 if (reqEntries == *allocated)
184 newAllocated = (*allocated + SIZE_OBJ_STEP);
186 /* throw out an extended debug message */
187 #ifdef DEBUG_PM_OBJ_EX
188 printf("SizeObjVertexData: reallocate (%d entries)\n",
191 /* try to reallocate */
192 vertexData = (TObjVertexData *)
193 _pico_realloc( (void *)&vertexData,
194 sizeof(TObjVertexData) * (*allocated),
195 sizeof(TObjVertexData) * (newAllocated));
197 /* reallocation failed */
198 if (vertexData == NULL)
201 /* reallocation succeeded */
202 *allocated = newAllocated;
203 *entries = reqEntries;
206 /* we're b0rked when we reach this */
210 static void FreeObjVertexData( TObjVertexData *vertexData )
212 if (vertexData != NULL)
214 free( (TObjVertexData *)vertexData );
218 static int _obj_mtl_load( picoModel_t *model )
220 picoShader_t *curShader = NULL;
222 picoByte_t *mtlBuffer;
227 if( model == NULL || model->fileName == NULL )
230 /* skip if we have a zero length model file name */
231 if (!strlen( model->fileName ))
235 #define _obj_mtl_error_return \
237 _pico_free_parser( p ); \
238 _pico_free_file( mtlBuffer ); \
239 _pico_free( fileName ); \
242 /* alloc copy of model file name */
243 fileName = _pico_clone_alloc( model->fileName );
244 if (fileName == NULL)
247 /* change extension of model file to .mtl */
248 _pico_setfext( fileName, "mtl" );
250 /* load .mtl file contents */
251 _pico_load_file( fileName,&mtlBuffer,&mtlBufSize );
254 if (mtlBufSize == 0) return 1; /* file is empty: no error */
255 if (mtlBufSize < 0) return 0; /* load failed: error */
257 /* create a new pico parser */
258 p = _pico_new_parser( mtlBuffer, mtlBufSize );
260 _obj_mtl_error_return;
262 /* doo teh .mtl parse */
265 /* get next token in material file */
266 if (_pico_parse( p,1 ) == NULL)
270 /* skip empty lines */
271 if (p->token == NULL || !strlen( p->token ))
274 /* skip comment lines */
275 if (p->token[0] == '#')
277 _pico_parse_skip_rest( p );
281 if (!_pico_stricmp(p->token,"newmtl"))
283 picoShader_t *shader;
286 /* get material name */
287 name = _pico_parse( p,0 );
289 /* validate material name */
290 if (name == NULL || !strlen(name))
292 _pico_printf( PICO_ERROR,"Missing material name in MTL, line %d.",p->curLine);
293 _obj_mtl_error_return;
295 /* create a new pico shader */
296 shader = PicoNewShader( model );
298 _obj_mtl_error_return;
300 /* set shader name */
301 PicoSetShaderName( shader,name );
303 /* assign pointer to current shader */
306 /* diffuse map name */
307 else if (!_pico_stricmp(p->token,"map_kd"))
311 /* pointer to current shader must be valid */
312 if (curShader == NULL)
313 _obj_mtl_error_return;
315 /* get material's diffuse map name */
316 mapName = _pico_parse( p,0 );
318 /* validate map name */
319 if (mapName == NULL || !strlen(mapName))
321 _pico_printf( PICO_ERROR,"Missing material map name in MTL, line %d.",p->curLine);
322 _obj_mtl_error_return;
324 /* set shader map name */
325 PicoSetShaderMapName( shader,mapName );
327 /* dissolve factor (pseudo transparency 0..1) */
328 /* where 0 means 100% transparent and 1 means opaque */
329 else if (!_pico_stricmp(p->token,"d"))
335 /* get dissolve factor */
336 if (!_pico_parse_float( p,&value ))
337 _obj_mtl_error_return;
339 /* set shader transparency */
340 PicoSetShaderTransparency( curShader,value );
342 /* get shader's diffuse color */
343 diffuse = PicoGetShaderDiffuseColor( curShader );
345 /* set diffuse alpha to transparency */
346 diffuse[ 3 ] = (picoByte_t)( value * 255.0 );
348 /* set shader's new diffuse color */
349 PicoSetShaderDiffuseColor( curShader,diffuse );
351 /* shininess (phong specular component) */
352 else if (!_pico_stricmp(p->token,"ns"))
355 * - well, this is some major obj spec fuckup once again. some
356 * apps store this in 0..1 range, others use 0..100 range,
357 * even others use 0..2048 range, and again others use the
358 * range 0..128, some even use 0..1000, 0..200, 400..700,
359 * honestly, what's up with the 3d app coders? happens when
360 * you smoke too much weed i guess. -sea
364 /* pointer to current shader must be valid */
365 if (curShader == NULL)
366 _obj_mtl_error_return;
368 /* get totally screwed up shininess (a random value in fact ;) */
369 if (!_pico_parse_float( p,&value ))
370 _obj_mtl_error_return;
372 /* okay, there is no way to set this correctly, so we simply */
373 /* try to guess a few ranges (most common ones i have seen) */
375 /* assume 0..2048 range */
377 value = 128.0 * (value / 2048.0);
378 /* assume 0..1000 range */
379 else if (value > 200)
380 value = 128.0 * (value / 1000.0);
381 /* assume 0..200 range */
382 else if (value > 100)
383 value = 128.0 * (value / 200.0);
384 /* assume 0..100 range */
386 value = 128.0 * (value / 100.0);
387 /* assume 0..1 range */
391 /* negative shininess is bad (yes, i have seen it...) */
392 if (value < 0.0) value = 0.0;
394 /* set the pico shininess value in range 0..127 */
395 /* geez, .obj is such a mess... */
396 PicoSetShaderShininess( curShader,value );
398 /* kol0r ambient (wut teh fuk does "ka" stand for?) */
399 else if (!_pico_stricmp(p->token,"ka"))
404 /* pointer to current shader must be valid */
405 if (curShader == NULL)
406 _obj_mtl_error_return;
408 /* get color vector */
409 if (!_pico_parse_vec( p,v ))
410 _obj_mtl_error_return;
412 /* scale to byte range */
413 color[ 0 ] = (picoByte_t)( v[ 0 ] * 255 );
414 color[ 1 ] = (picoByte_t)( v[ 1 ] * 255 );
415 color[ 2 ] = (picoByte_t)( v[ 2 ] * 255 );
416 color[ 3 ] = (picoByte_t)( 255 );
418 /* set ambient color */
419 PicoSetShaderAmbientColor( curShader,color );
422 else if (!_pico_stricmp(p->token,"kd"))
427 /* pointer to current shader must be valid */
428 if (curShader == NULL)
429 _obj_mtl_error_return;
431 /* get color vector */
432 if (!_pico_parse_vec( p,v ))
433 _obj_mtl_error_return;
435 /* scale to byte range */
436 color[ 0 ] = (picoByte_t)( v[ 0 ] * 255 );
437 color[ 1 ] = (picoByte_t)( v[ 1 ] * 255 );
438 color[ 2 ] = (picoByte_t)( v[ 2 ] * 255 );
439 color[ 3 ] = (picoByte_t)( 255 );
441 /* set diffuse color */
442 PicoSetShaderDiffuseColor( curShader,color );
445 else if (!_pico_stricmp(p->token,"ks"))
450 /* pointer to current shader must be valid */
451 if (curShader == NULL)
452 _obj_mtl_error_return;
454 /* get color vector */
455 if (!_pico_parse_vec( p,v ))
456 _obj_mtl_error_return;
458 /* scale to byte range */
459 color[ 0 ] = (picoByte_t)( v[ 0 ] * 255 );
460 color[ 1 ] = (picoByte_t)( v[ 1 ] * 255 );
461 color[ 2 ] = (picoByte_t)( v[ 2 ] * 255 );
462 color[ 3 ] = (picoByte_t)( 255 );
464 /* set specular color */
465 PicoSetShaderSpecularColor( curShader,color );
468 /* skip rest of line */
469 _pico_parse_skip_rest( p );
472 /* free parser, file buffer, and file name */
473 _pico_free_parser( p );
474 _pico_free_file( mtlBuffer );
475 _pico_free( fileName );
477 /* return with success */
482 * loads a wavefront obj model file.
484 static picoModel_t *_obj_load( PM_PARAMS_LOAD )
486 TObjVertexData *vertexData = NULL;
488 picoSurface_t *curSurface = NULL;
499 #define _obj_error_return(m) \
501 _pico_printf( PICO_ERROR,"%s in OBJ, line %d.",m,p->curLine); \
502 _pico_free_parser( p ); \
503 FreeObjVertexData( vertexData ); \
504 PicoFreeModel( model ); \
507 /* alllocate a new pico parser */
508 p = _pico_new_parser( (picoByte_t *)buffer,bufSize );
509 if (p == NULL) return NULL;
511 /* create a new pico model */
512 model = PicoNewModel();
515 _pico_free_parser( p );
519 PicoSetModelFrameNum( model,frameNum );
520 PicoSetModelName( model,fileName );
521 PicoSetModelFileName( model,fileName );
523 /* try loading the materials; we don't handle the result */
525 _obj_mtl_load( model );
528 /* parse obj line by line */
531 /* get first token on line */
532 if (_pico_parse_first( p ) == NULL)
535 /* skip empty lines */
536 if (p->token == NULL || !strlen( p->token ))
539 /* skip comment lines */
540 if (p->token[0] == '#')
542 _pico_parse_skip_rest( p );
546 if (!_pico_stricmp(p->token,"v"))
548 TObjVertexData *data;
551 vertexData = SizeObjVertexData( vertexData,numVerts+1,&entries,&allocated );
552 if (vertexData == NULL)
553 _obj_error_return("Realloc of vertex data failed (1)");
555 data = &vertexData[ numVerts++ ];
557 /* get and copy vertex */
558 if (!_pico_parse_vec( p,v ))
559 _obj_error_return("Vertex parse error");
561 _pico_copy_vec( v,data->v );
563 #ifdef DEBUG_PM_OBJ_EX
564 printf("Vertex: x: %f y: %f z: %f\n",v[0],v[1],v[2]);
568 else if (!_pico_stricmp(p->token,"vt"))
570 TObjVertexData *data;
573 vertexData = SizeObjVertexData( vertexData,numUVs+1,&entries,&allocated );
574 if (vertexData == NULL)
575 _obj_error_return("Realloc of vertex data failed (2)");
577 data = &vertexData[ numUVs++ ];
579 /* get and copy tex coord */
580 if (!_pico_parse_vec2( p,coord ))
581 _obj_error_return("UV coord parse error");
583 _pico_copy_vec2( coord,data->vt );
585 #ifdef DEBUG_PM_OBJ_EX
586 printf("TexCoord: u: %f v: %f\n",coord[0],coord[1]);
590 else if (!_pico_stricmp(p->token,"vn"))
592 TObjVertexData *data;
595 vertexData = SizeObjVertexData( vertexData,numNormals+1,&entries,&allocated );
596 if (vertexData == NULL)
597 _obj_error_return("Realloc of vertex data failed (3)");
599 data = &vertexData[ numNormals++ ];
601 /* get and copy vertex normal */
602 if (!_pico_parse_vec( p,n ))
603 _obj_error_return("Vertex normal parse error");
605 _pico_copy_vec( n,data->vn );
607 #ifdef DEBUG_PM_OBJ_EX
608 printf("Normal: x: %f y: %f z: %f\n",n[0],n[1],n[2]);
611 /* new group (for us this means a new surface) */
612 else if (!_pico_stricmp(p->token,"g"))
614 picoSurface_t *newSurface;
617 /* get first group name (ignore 2nd,3rd,etc.) */
618 groupName = _pico_parse( p,0 );
619 if (groupName == NULL || !strlen(groupName))
621 /* some obj exporters feel like they don't need to */
622 /* supply a group name. so we gotta handle it here */
624 strcpy( p->token,"default" );
625 groupName = p->token;
627 _obj_error_return("Invalid or missing group name");
630 /* allocate a pico surface */
631 newSurface = PicoNewSurface( model );
632 if (newSurface == NULL)
633 _obj_error_return("Error allocating surface");
635 /* reset face index for surface */
638 /* set ptr to current surface */
639 curSurface = newSurface;
641 /* we use triangle meshes */
642 PicoSetSurfaceType( newSurface,PICO_TRIANGLES );
644 /* set surface name */
645 PicoSetSurfaceName( newSurface,groupName );
647 #ifdef DEBUG_PM_OBJ_EX
648 printf("Group: '%s'\n",groupName);
651 /* face (oh jesus, hopefully this will do the job right ;) */
652 else if (!_pico_stricmp(p->token,"f"))
654 /* okay, this is a mess. some 3d apps seem to try being unique, */
655 /* hello cinema4d & 3d exploration, feel good today?, and save */
656 /* this crap in tons of different formats. gah, those screwed */
657 /* coders. tho the wavefront obj standard defines exactly two */
658 /* ways of storing face information. so, i really won't support */
659 /* such stupid extravaganza here! */
661 picoVec3_t verts [ 4 ];
662 picoVec3_t normals[ 4 ];
663 picoVec2_t coords [ 4 ];
666 int ivt[ 4 ], has_vt = 0;
667 int ivn[ 4 ], has_vn = 0;
673 /* group defs *must* come before faces */
674 if (curSurface == NULL)
675 _obj_error_return("No group defined for faces");
677 #ifdef DEBUG_PM_OBJ_EX
680 /* read vertex/uv/normal indices for the first three face */
681 /* vertices (cause we only support triangles) into 'i*[]' */
682 /* store the actual vertex/uv/normal data in three arrays */
683 /* called 'verts','coords' and 'normals'. */
688 /* get next vertex index string (different */
689 /* formats are handled below) */
690 str = _pico_parse( p,0 );
693 /* just break for quads */
696 /* error otherwise */
697 _obj_error_return("Face parse error");
699 /* if this is the fourth index string we're */
700 /* parsing we assume that we have a quad */
704 /* get slash count once */
707 slashcount = _pico_strchcount( str,'/' );
708 doubleslash = strstr(str,"//") != NULL;
710 /* handle format 'v//vn' */
711 if (doubleslash && (slashcount == 2))
714 sscanf( str,"%d//%d",&iv[ i ],&ivn[ i ] );
716 /* handle format 'v/vt/vn' */
717 else if (!doubleslash && (slashcount == 2))
719 has_v = has_vt = has_vn = 1;
720 sscanf( str,"%d/%d/%d",&iv[ i ],&ivt[ i ],&ivn[ i ] );
722 /* handle format 'v/vt' (non-standard fuckage) */
723 else if (!doubleslash && (slashcount == 1))
726 sscanf( str,"%d/%d",&iv[ i ],&ivt[ i ] );
728 /* else assume face format 'v' */
729 /* (must have been invented by some bored granny) */
731 /* get single vertex index */
733 iv[ i ] = atoi( str );
735 /* either invalid face format or out of range */
737 _obj_error_return("Invalid face format");
739 /* fix useless back references */
740 /* todo: check if this works as it is supposed to */
742 /* assign new indices */
743 if (iv [ i ] < 0) iv [ i ] = (numVerts - iv [ i ]);
744 if (ivt[ i ] < 0) ivt[ i ] = (numUVs - ivt[ i ]);
745 if (ivn[ i ] < 0) ivn[ i ] = (numNormals - ivn[ i ]);
747 /* validate indices */
748 /* - commented out. index range checks will trigger
749 if (iv [ i ] < 1) iv [ i ] = 1;
750 if (ivt[ i ] < 1) ivt[ i ] = 1;
751 if (ivn[ i ] < 1) ivn[ i ] = 1;
753 /* set vertex origin */
756 /* check vertex index range */
757 if (iv[ i ] < 1 || iv[ i ] > numVerts)
758 _obj_error_return("Vertex index out of range");
760 /* get vertex data */
761 verts[ i ][ 0 ] = vertexData[ iv[ i ] - 1 ].v[ 0 ];
762 verts[ i ][ 1 ] = vertexData[ iv[ i ] - 1 ].v[ 1 ];
763 verts[ i ][ 2 ] = vertexData[ iv[ i ] - 1 ].v[ 2 ];
765 /* set vertex normal */
768 /* check normal index range */
769 if (ivn[ i ] < 1 || ivn[ i ] > numNormals)
770 _obj_error_return("Normal index out of range");
772 /* get normal data */
773 normals[ i ][ 0 ] = vertexData[ ivn[ i ] - 1 ].vn[ 0 ];
774 normals[ i ][ 1 ] = vertexData[ ivn[ i ] - 1 ].vn[ 1 ];
775 normals[ i ][ 2 ] = vertexData[ ivn[ i ] - 1 ].vn[ 2 ];
777 /* set texture coordinate */
780 /* check uv index range */
781 if (ivt[ i ] < 1 || ivt[ i ] > numUVs)
782 _obj_error_return("UV coord index out of range");
784 /* get uv coord data */
785 coords[ i ][ 0 ] = vertexData[ ivt[ i ] - 1 ].vt[ 0 ];
786 coords[ i ][ 1 ] = vertexData[ ivt[ i ] - 1 ].vt[ 1 ];
787 coords[ i ][ 1 ] = -coords[ i ][ 1 ];
789 #ifdef DEBUG_PM_OBJ_EX
790 printf("(%4d",iv[ i ]);
791 if (has_vt) printf(" %4d",ivt[ i ]);
792 if (has_vn) printf(" %4d",ivn[ i ]);
796 #ifdef DEBUG_PM_OBJ_EX
799 /* now that we have extracted all the indices and have */
800 /* read the actual data we need to assign all the crap */
801 /* to our current pico surface */
805 if (have_quad) max = 4;
807 /* assign all surface information */
808 for (i=0; i<max; i++)
810 /*if( has_v )*/ PicoSetSurfaceXYZ ( curSurface, (curVertex + i), verts [ i ] );
811 /*if( has_vt )*/ PicoSetSurfaceST ( curSurface,0,(curVertex + i), coords [ i ] );
812 /*if( has_vn )*/ PicoSetSurfaceNormal( curSurface, (curVertex + i), normals[ i ] );
814 /* add our triangle (A B C) */
815 PicoSetSurfaceIndex( curSurface,(curFace * 3 + 2),(picoIndex_t)( curVertex + 0 ) );
816 PicoSetSurfaceIndex( curSurface,(curFace * 3 + 1),(picoIndex_t)( curVertex + 1 ) );
817 PicoSetSurfaceIndex( curSurface,(curFace * 3 + 0),(picoIndex_t)( curVertex + 2 ) );
820 /* if we don't have a simple triangle, but a quad... */
823 /* we have to add another triangle (2nd half of quad which is A C D) */
824 PicoSetSurfaceIndex( curSurface,(curFace * 3 + 2),(picoIndex_t)( curVertex + 0 ) );
825 PicoSetSurfaceIndex( curSurface,(curFace * 3 + 1),(picoIndex_t)( curVertex + 2 ) );
826 PicoSetSurfaceIndex( curSurface,(curFace * 3 + 0),(picoIndex_t)( curVertex + 3 ) );
829 /* increase vertex count */
833 /* skip unparsed rest of line and continue */
834 _pico_parse_skip_rest( p );
836 /* free memory used by temporary vertexdata */
837 FreeObjVertexData( vertexData );
839 /* return allocated pico model */
844 /* pico file format module definition */
845 const picoModule_t picoModuleOBJ =
847 "0.6-b", /* module version string */
848 "Wavefront ASCII", /* module display name */
849 "seaw0lf", /* author's name */
850 "2002 seaw0lf", /* module copyright */
852 "obj",NULL,NULL,NULL /* default extensions to use */
854 _obj_canload, /* validation routine */
855 _obj_load, /* load routine */
856 NULL, /* save validation routine */
857 NULL /* save routine */