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 );
219 static int _obj_mtl_load( picoModel_t *model )
221 //picoShader_t *curShader = NULL;
223 picoByte_t *mtlBuffer;
228 if( model == NULL || model->fileName == NULL )
231 /* skip if we have a zero length model file name */
232 if (!strlen( model->fileName ))
236 #define _obj_mtl_error_return \
238 _pico_free_parser( p ); \
239 _pico_free_file( mtlBuffer ); \
240 _pico_free( fileName ); \
243 /* alloc copy of model file name */
244 fileName = _pico_clone_alloc( model->fileName,-1 );
245 if (fileName == NULL)
248 /* change extension of model file to .mtl */
249 _pico_setfext( fileName, "mtl" );
251 /* load .mtl file contents */
252 _pico_load_file( fileName,&mtlBuffer,&mtlBufSize );
255 if (mtlBufSize == 0) return 1; /* file is empty: no error */
256 if (mtlBufSize < 0) return 0; /* load failed: error */
258 /* create a new pico parser */
259 p = _pico_new_parser( mtlBuffer, mtlBufSize );
261 _obj_mtl_error_return;
263 /* doo teh .mtl parse */
266 /* get next token in material file */
267 if (_pico_parse( p,1 ) == NULL)
271 /* skip empty lines */
272 if (p->token == NULL || !strlen( p->token ))
275 /* skip comment lines */
276 if (p->token[0] == '#')
278 _pico_parse_skip_rest( p );
282 if (!_pico_stricmp(p->token,"newmtl"))
284 picoShader_t *shader;
287 /* get material name */
288 name = _pico_parse( p,0 );
290 /* validate material name */
291 if (name == NULL || !strlen(name))
293 _pico_printf( PICO_ERROR,"Missing material name in MTL, line %d.",p->curLine);
294 _obj_mtl_error_return;
296 /* create a new pico shader */
297 shader = PicoNewShader( model );
299 _obj_mtl_error_return;
301 /* set shader name */
302 PicoSetShaderName( shader,name );
304 /* assign pointer to current shader */
307 /* diffuse map name */
308 else if (!_pico_stricmp(p->token,"map_kd"))
312 /* pointer to current shader must be valid */
313 if (curShader == NULL)
314 _obj_mtl_error_return;
316 /* get material's diffuse map name */
317 mapName = _pico_parse( p,0 );
319 /* validate map name */
320 if (mapName == NULL || !strlen(mapName))
322 _pico_printf( PICO_ERROR,"Missing material map name in MTL, line %d.",p->curLine);
323 _obj_mtl_error_return;
325 /* set shader map name */
326 PicoSetShaderMapName( shader,mapName );
328 /* dissolve factor (pseudo transparency 0..1) */
329 /* where 0 means 100% transparent and 1 means opaque */
330 else if (!_pico_stricmp(p->token,"d"))
336 /* get dissolve factor */
337 if (!_pico_parse_float( p,&value ))
338 _obj_mtl_error_return;
340 /* set shader transparency */
341 PicoSetShaderTransparency( curShader,value );
343 /* get shader's diffuse color */
344 diffuse = PicoGetShaderDiffuseColor( curShader );
346 /* set diffuse alpha to transparency */
347 diffuse[ 3 ] = (picoByte_t)( value * 255.0 );
349 /* set shader's new diffuse color */
350 PicoSetShaderDiffuseColor( curShader,diffuse );
352 /* shininess (phong specular component) */
353 else if (!_pico_stricmp(p->token,"ns"))
356 * - well, this is some major obj spec fuckup once again. some
357 * apps store this in 0..1 range, others use 0..100 range,
358 * even others use 0..2048 range, and again others use the
359 * range 0..128, some even use 0..1000, 0..200, 400..700,
360 * honestly, what's up with the 3d app coders? happens when
361 * you smoke too much weed i guess. -sea
365 /* pointer to current shader must be valid */
366 if (curShader == NULL)
367 _obj_mtl_error_return;
369 /* get totally screwed up shininess (a random value in fact ;) */
370 if (!_pico_parse_float( p,&value ))
371 _obj_mtl_error_return;
373 /* okay, there is no way to set this correctly, so we simply */
374 /* try to guess a few ranges (most common ones i have seen) */
376 /* assume 0..2048 range */
378 value = 128.0 * (value / 2048.0);
379 /* assume 0..1000 range */
380 else if (value > 200)
381 value = 128.0 * (value / 1000.0);
382 /* assume 0..200 range */
383 else if (value > 100)
384 value = 128.0 * (value / 200.0);
385 /* assume 0..100 range */
387 value = 128.0 * (value / 100.0);
388 /* assume 0..1 range */
392 /* negative shininess is bad (yes, i have seen it...) */
393 if (value < 0.0) value = 0.0;
395 /* set the pico shininess value in range 0..127 */
396 /* geez, .obj is such a mess... */
397 PicoSetShaderShininess( curShader,value );
399 /* kol0r ambient (wut teh fuk does "ka" stand for?) */
400 else if (!_pico_stricmp(p->token,"ka"))
405 /* pointer to current shader must be valid */
406 if (curShader == NULL)
407 _obj_mtl_error_return;
409 /* get color vector */
410 if (!_pico_parse_vec( p,v ))
411 _obj_mtl_error_return;
413 /* scale to byte range */
414 color[ 0 ] = (picoByte_t)( v[ 0 ] * 255 );
415 color[ 1 ] = (picoByte_t)( v[ 1 ] * 255 );
416 color[ 2 ] = (picoByte_t)( v[ 2 ] * 255 );
417 color[ 3 ] = (picoByte_t)( 255 );
419 /* set ambient color */
420 PicoSetShaderAmbientColor( curShader,color );
423 else if (!_pico_stricmp(p->token,"kd"))
428 /* pointer to current shader must be valid */
429 if (curShader == NULL)
430 _obj_mtl_error_return;
432 /* get color vector */
433 if (!_pico_parse_vec( p,v ))
434 _obj_mtl_error_return;
436 /* scale to byte range */
437 color[ 0 ] = (picoByte_t)( v[ 0 ] * 255 );
438 color[ 1 ] = (picoByte_t)( v[ 1 ] * 255 );
439 color[ 2 ] = (picoByte_t)( v[ 2 ] * 255 );
440 color[ 3 ] = (picoByte_t)( 255 );
442 /* set diffuse color */
443 PicoSetShaderDiffuseColor( curShader,color );
446 else if (!_pico_stricmp(p->token,"ks"))
451 /* pointer to current shader must be valid */
452 if (curShader == NULL)
453 _obj_mtl_error_return;
455 /* get color vector */
456 if (!_pico_parse_vec( p,v ))
457 _obj_mtl_error_return;
459 /* scale to byte range */
460 color[ 0 ] = (picoByte_t)( v[ 0 ] * 255 );
461 color[ 1 ] = (picoByte_t)( v[ 1 ] * 255 );
462 color[ 2 ] = (picoByte_t)( v[ 2 ] * 255 );
463 color[ 3 ] = (picoByte_t)( 255 );
465 /* set specular color */
466 PicoSetShaderSpecularColor( curShader,color );
469 /* skip rest of line */
470 _pico_parse_skip_rest( p );
473 /* free parser, file buffer, and file name */
474 _pico_free_parser( p );
475 _pico_free_file( mtlBuffer );
476 _pico_free( fileName );
478 /* return with success */
484 * loads a wavefront obj model file.
486 static picoModel_t *_obj_load( PM_PARAMS_LOAD )
488 TObjVertexData *vertexData = NULL;
490 picoSurface_t *curSurface = NULL;
501 #define _obj_error_return(m) \
503 _pico_printf( PICO_ERROR,"%s in OBJ, line %d.",m,p->curLine); \
504 _pico_free_parser( p ); \
505 FreeObjVertexData( vertexData ); \
506 PicoFreeModel( model ); \
509 /* alllocate a new pico parser */
510 p = _pico_new_parser( (picoByte_t *)buffer,bufSize );
511 if (p == NULL) return NULL;
513 /* create a new pico model */
514 model = PicoNewModel();
517 _pico_free_parser( p );
521 PicoSetModelFrameNum( model,frameNum );
522 PicoSetModelName( model,fileName );
523 PicoSetModelFileName( model,fileName );
525 /* try loading the materials; we don't handle the result */
527 _obj_mtl_load( model );
530 /* parse obj line by line */
533 /* get first token on line */
534 if (_pico_parse_first( p ) == NULL)
537 /* skip empty lines */
538 if (p->token == NULL || !strlen( p->token ))
541 /* skip comment lines */
542 if (p->token[0] == '#')
544 _pico_parse_skip_rest( p );
548 if (!_pico_stricmp(p->token,"v"))
550 TObjVertexData *data;
553 vertexData = SizeObjVertexData( vertexData,numVerts+1,&entries,&allocated );
554 if (vertexData == NULL)
555 _obj_error_return("Realloc of vertex data failed (1)");
557 data = &vertexData[ numVerts++ ];
559 /* get and copy vertex */
560 if (!_pico_parse_vec( p,v ))
561 _obj_error_return("Vertex parse error");
563 _pico_copy_vec( v,data->v );
565 #ifdef DEBUG_PM_OBJ_EX
566 printf("Vertex: x: %f y: %f z: %f\n",v[0],v[1],v[2]);
570 else if (!_pico_stricmp(p->token,"vt"))
572 TObjVertexData *data;
575 vertexData = SizeObjVertexData( vertexData,numUVs+1,&entries,&allocated );
576 if (vertexData == NULL)
577 _obj_error_return("Realloc of vertex data failed (2)");
579 data = &vertexData[ numUVs++ ];
581 /* get and copy tex coord */
582 if (!_pico_parse_vec2( p,coord ))
583 _obj_error_return("UV coord parse error");
585 _pico_copy_vec2( coord,data->vt );
587 #ifdef DEBUG_PM_OBJ_EX
588 printf("TexCoord: u: %f v: %f\n",coord[0],coord[1]);
592 else if (!_pico_stricmp(p->token,"vn"))
594 TObjVertexData *data;
597 vertexData = SizeObjVertexData( vertexData,numNormals+1,&entries,&allocated );
598 if (vertexData == NULL)
599 _obj_error_return("Realloc of vertex data failed (3)");
601 data = &vertexData[ numNormals++ ];
603 /* get and copy vertex normal */
604 if (!_pico_parse_vec( p,n ))
605 _obj_error_return("Vertex normal parse error");
607 _pico_copy_vec( n,data->vn );
609 #ifdef DEBUG_PM_OBJ_EX
610 printf("Normal: x: %f y: %f z: %f\n",n[0],n[1],n[2]);
613 /* new group (for us this means a new surface) */
614 else if (!_pico_stricmp(p->token,"g"))
616 picoSurface_t *newSurface;
619 /* get first group name (ignore 2nd,3rd,etc.) */
620 groupName = _pico_parse( p,0 );
621 if (groupName == NULL || !strlen(groupName))
623 /* some obj exporters feel like they don't need to */
624 /* supply a group name. so we gotta handle it here */
626 strcpy( p->token,"default" );
627 groupName = p->token;
629 _obj_error_return("Invalid or missing group name");
632 /* allocate a pico surface */
633 newSurface = PicoNewSurface( model );
634 if (newSurface == NULL)
635 _obj_error_return("Error allocating surface");
637 /* reset face index for surface */
640 /* set ptr to current surface */
641 curSurface = newSurface;
643 /* we use triangle meshes */
644 PicoSetSurfaceType( newSurface,PICO_TRIANGLES );
646 /* set surface name */
647 PicoSetSurfaceName( newSurface,groupName );
649 #ifdef DEBUG_PM_OBJ_EX
650 printf("Group: '%s'\n",groupName);
653 /* face (oh jesus, hopefully this will do the job right ;) */
654 else if (!_pico_stricmp(p->token,"f"))
656 /* okay, this is a mess. some 3d apps seem to try being unique, */
657 /* hello cinema4d & 3d exploration, feel good today?, and save */
658 /* this crap in tons of different formats. gah, those screwed */
659 /* coders. tho the wavefront obj standard defines exactly two */
660 /* ways of storing face information. so, i really won't support */
661 /* such stupid extravaganza here! */
663 picoVec3_t verts [ 4 ];
664 picoVec3_t normals[ 4 ];
665 picoVec2_t coords [ 4 ];
668 int ivt[ 4 ], has_vt = 0;
669 int ivn[ 4 ], has_vn = 0;
675 /* group defs *must* come before faces */
676 if (curSurface == NULL)
677 _obj_error_return("No group defined for faces");
679 #ifdef DEBUG_PM_OBJ_EX
682 /* read vertex/uv/normal indices for the first three face */
683 /* vertices (cause we only support triangles) into 'i*[]' */
684 /* store the actual vertex/uv/normal data in three arrays */
685 /* called 'verts','coords' and 'normals'. */
690 /* get next vertex index string (different */
691 /* formats are handled below) */
692 str = _pico_parse( p,0 );
695 /* just break for quads */
698 /* error otherwise */
699 _obj_error_return("Face parse error");
701 /* if this is the fourth index string we're */
702 /* parsing we assume that we have a quad */
706 /* get slash count once */
709 slashcount = _pico_strchcount( str,'/' );
710 doubleslash = strstr(str,"//") != NULL;
712 /* handle format 'v//vn' */
713 if (doubleslash && (slashcount == 2))
716 sscanf( str,"%d//%d",&iv[ i ],&ivn[ i ] );
718 /* handle format 'v/vt/vn' */
719 else if (!doubleslash && (slashcount == 2))
721 has_v = has_vt = has_vn = 1;
722 sscanf( str,"%d/%d/%d",&iv[ i ],&ivt[ i ],&ivn[ i ] );
724 /* handle format 'v/vt' (non-standard fuckage) */
725 else if (!doubleslash && (slashcount == 1))
728 sscanf( str,"%d/%d",&iv[ i ],&ivt[ i ] );
730 /* else assume face format 'v' */
731 /* (must have been invented by some bored granny) */
733 /* get single vertex index */
735 iv[ i ] = atoi( str );
737 /* either invalid face format or out of range */
739 _obj_error_return("Invalid face format");
741 /* fix useless back references */
742 /* todo: check if this works as it is supposed to */
744 /* assign new indices */
745 if (iv [ i ] < 0) iv [ i ] = (numVerts - iv [ i ]);
746 if (ivt[ i ] < 0) ivt[ i ] = (numUVs - ivt[ i ]);
747 if (ivn[ i ] < 0) ivn[ i ] = (numNormals - ivn[ i ]);
749 /* validate indices */
750 /* - commented out. index range checks will trigger
751 if (iv [ i ] < 1) iv [ i ] = 1;
752 if (ivt[ i ] < 1) ivt[ i ] = 1;
753 if (ivn[ i ] < 1) ivn[ i ] = 1;
755 /* set vertex origin */
758 /* check vertex index range */
759 if (iv[ i ] < 1 || iv[ i ] > numVerts)
760 _obj_error_return("Vertex index out of range");
762 /* get vertex data */
763 verts[ i ][ 0 ] = vertexData[ iv[ i ] - 1 ].v[ 0 ];
764 verts[ i ][ 1 ] = vertexData[ iv[ i ] - 1 ].v[ 1 ];
765 verts[ i ][ 2 ] = vertexData[ iv[ i ] - 1 ].v[ 2 ];
767 /* set vertex normal */
770 /* check normal index range */
771 if (ivn[ i ] < 1 || ivn[ i ] > numNormals)
772 _obj_error_return("Normal index out of range");
774 /* get normal data */
775 normals[ i ][ 0 ] = vertexData[ ivn[ i ] - 1 ].vn[ 0 ];
776 normals[ i ][ 1 ] = vertexData[ ivn[ i ] - 1 ].vn[ 1 ];
777 normals[ i ][ 2 ] = vertexData[ ivn[ i ] - 1 ].vn[ 2 ];
779 /* set texture coordinate */
782 /* check uv index range */
783 if (ivt[ i ] < 1 || ivt[ i ] > numUVs)
784 _obj_error_return("UV coord index out of range");
786 /* get uv coord data */
787 coords[ i ][ 0 ] = vertexData[ ivt[ i ] - 1 ].vt[ 0 ];
788 coords[ i ][ 1 ] = vertexData[ ivt[ i ] - 1 ].vt[ 1 ];
789 coords[ i ][ 1 ] = -coords[ i ][ 1 ];
791 #ifdef DEBUG_PM_OBJ_EX
792 printf("(%4d",iv[ i ]);
793 if (has_vt) printf(" %4d",ivt[ i ]);
794 if (has_vn) printf(" %4d",ivn[ i ]);
798 #ifdef DEBUG_PM_OBJ_EX
801 /* now that we have extracted all the indices and have */
802 /* read the actual data we need to assign all the crap */
803 /* to our current pico surface */
807 if (have_quad) max = 4;
809 /* assign all surface information */
810 for (i=0; i<max; i++)
812 /*if( has_v )*/ PicoSetSurfaceXYZ ( curSurface, (curVertex + i), verts [ i ] );
813 /*if( has_vt )*/ PicoSetSurfaceST ( curSurface,0,(curVertex + i), coords [ i ] );
814 /*if( has_vn )*/ PicoSetSurfaceNormal( curSurface, (curVertex + i), normals[ i ] );
816 /* add our triangle (A B C) */
817 PicoSetSurfaceIndex( curSurface,(curFace * 3 + 2),(picoIndex_t)( curVertex + 0 ) );
818 PicoSetSurfaceIndex( curSurface,(curFace * 3 + 1),(picoIndex_t)( curVertex + 1 ) );
819 PicoSetSurfaceIndex( curSurface,(curFace * 3 + 0),(picoIndex_t)( curVertex + 2 ) );
822 /* if we don't have a simple triangle, but a quad... */
825 /* we have to add another triangle (2nd half of quad which is A C D) */
826 PicoSetSurfaceIndex( curSurface,(curFace * 3 + 2),(picoIndex_t)( curVertex + 0 ) );
827 PicoSetSurfaceIndex( curSurface,(curFace * 3 + 1),(picoIndex_t)( curVertex + 2 ) );
828 PicoSetSurfaceIndex( curSurface,(curFace * 3 + 0),(picoIndex_t)( curVertex + 3 ) );
831 /* increase vertex count */
835 /* skip unparsed rest of line and continue */
836 _pico_parse_skip_rest( p );
838 /* free memory used by temporary vertexdata */
839 FreeObjVertexData( vertexData );
841 /* return allocated pico model */
846 /* pico file format module definition */
847 const picoModule_t picoModuleOBJ =
849 "0.6-b", /* module version string */
850 "Wavefront ASCII", /* module display name */
851 "seaw0lf", /* author's name */
852 "2002 seaw0lf", /* module copyright */
854 "obj",NULL,NULL,NULL /* default extensions to use */
856 _obj_canload, /* validation routine */
857 _obj_load, /* load routine */
858 NULL, /* save validation routine */
859 NULL /* save routine */