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"
44 static picoColor_t white = { 255,255,255,255 };
47 * - 3ds file version is stored in pico special field 0 on load (ydnar: removed)
49 * - sometimes there is one unnamed surface 0 having 0 verts as
50 * well as 0 faces. this error occurs since pm 0.6 (ydnar?)
52 /* uncomment when debugging this module */
53 /* #define DEBUG_PM_3DS
54 #define DEBUG_PM_3DS_EX */
56 /* structure holding persistent 3ds loader specific data used */
57 /* to store formerly static vars to keep the module reentrant */
58 /* safe. put everything that needs to be static in here. */
59 typedef struct S3dsLoaderPers
61 picoModel_t *model; /* ptr to output model */
62 picoSurface_t *surface; /* ptr to current surface */
63 picoShader_t *shader; /* ptr to current shader */
64 picoByte_t *bufptr; /* ptr to raw data */
65 char *basename; /* ptr to model base name (eg. jeep) */
71 /* 3ds chunk types that we use */
77 CHUNK_VERSION = 0x0002,
78 CHUNK_EDITOR_CONFIG = 0x3D3E,
79 CHUNK_EDITOR_DATA = 0x3D3D,
80 CHUNK_KEYFRAME_DATA = 0xB000,
82 /* editor data sub chunks */
83 CHUNK_MATERIAL = 0xAFFF,
84 CHUNK_OBJECT = 0x4000,
86 /* material sub chunks */
87 CHUNK_MATNAME = 0xA000,
88 CHUNK_MATDIFFUSE = 0xA020,
89 CHUNK_MATMAP = 0xA200,
90 CHUNK_MATMAPFILE = 0xA300,
92 /* lets us know we're reading a new object */
93 CHUNK_OBJECT_MESH = 0x4100,
95 /* object mesh sub chunks */
96 CHUNK_OBJECT_VERTICES = 0x4110,
97 CHUNK_OBJECT_FACES = 0x4120,
98 CHUNK_OBJECT_MATERIAL = 0x4130,
99 CHUNK_OBJECT_UV = 0x4140,
109 { CHUNK_MAIN, "CHUNK_MAIN" },
110 { CHUNK_VERSION, "CHUNK_VERSION" },
111 { CHUNK_EDITOR_CONFIG, "CHUNK_EDITOR_CONFIG" },
112 { CHUNK_EDITOR_DATA, "CHUNK_EDITOR_DATA" },
113 { CHUNK_KEYFRAME_DATA, "CHUNK_KEYFRAME_DATA" },
114 { CHUNK_MATERIAL, "CHUNK_MATERIAL" },
115 { CHUNK_OBJECT, "CHUNK_OBJECT" },
116 { CHUNK_MATNAME, "CHUNK_MATNAME" },
117 { CHUNK_MATDIFFUSE, "CHUNK_MATDIFFUSE" },
118 { CHUNK_MATMAP, "CHUNK_MATMAP" },
119 { CHUNK_MATMAPFILE, "CHUNK_MATMAPFILE" },
120 { CHUNK_OBJECT_MESH, "CHUNK_OBJECT_MESH" },
121 { CHUNK_OBJECT_VERTICES, "CHUNK_OBJECT_VERTICES" },
122 { CHUNK_OBJECT_FACES, "CHUNK_OBJECT_FACES" },
123 { CHUNK_OBJECT_MATERIAL, "CHUNK_OBJECT_MATERIAL" },
124 { CHUNK_OBJECT_UV, "CHUNK_OBJECT_UV" },
127 static char *DebugGetChunkName( int id ){
128 int i,max; /* imax? ;) */
129 max = sizeof( debugChunkNames ) / sizeof( debugChunkNames[0] );
131 for ( i = 0; i < max; i++ )
133 if ( debugChunkNames[i].id == id ) {
134 /* gaynux update -sea */
135 return _pico_strlwr( debugChunkNames[i].name );
138 return "chunk_unknown";
140 #endif /*DEBUG_PM_3DS*/
142 /* this funky loader needs byte alignment */
143 #pragma pack(push, 1)
145 typedef struct S3dsIndices
147 unsigned short a,b,c;
148 unsigned short visible;
152 typedef struct S3dsChunk
159 /* restore previous data alignment */
163 * validates an autodesk 3ds model file.
165 static int _3ds_canload( PM_PARAMS_CANLOAD ){
166 const T3dsChunk *chunk;
169 if ( bufSize < (int) sizeof( T3dsChunk ) ) {
170 return PICO_PMV_ERROR_SIZE;
173 /* get pointer to 3ds header chunk */
174 chunk = (const T3dsChunk *)buffer;
176 /* check data length */
177 if ( bufSize < (int) _pico_little_long( chunk->len ) ) {
178 return PICO_PMV_ERROR_SIZE;
181 /* check 3ds magic */
182 if ( _pico_little_short( chunk->id ) != CHUNK_MAIN ) {
183 return PICO_PMV_ERROR_IDENT;
186 /* file seems to be a valid 3ds */
190 static T3dsChunk *GetChunk( T3dsLoaderPers *pers ){
194 if ( pers->cofs > pers->maxofs ) {
199 /* printf("GetChunk: pers->cofs %x\n",pers->cofs); */
201 /* fill in pointer to chunk */
202 chunk = (T3dsChunk *)&pers->bufptr[ pers->cofs ];
207 chunk->id = _pico_little_short( chunk->id );
208 chunk->len = _pico_little_long( chunk->len );
210 /* advance in buffer */
211 pers->cofs += sizeof( T3dsChunk );
217 static int GetASCIIZ( T3dsLoaderPers *pers, char *dest, int max ){
223 ch = pers->bufptr[ pers->cofs++ ];
227 if ( pers->cofs >= pers->maxofs ) {
240 static picoByte_t GetByte( T3dsLoaderPers *pers ){
244 if ( pers->cofs > pers->maxofs ) {
248 /* get and return value */
249 value = (picoByte_t *)( pers->bufptr + pers->cofs );
254 static int GetWord( T3dsLoaderPers *pers ){
255 unsigned short *value;
258 if ( pers->cofs > pers->maxofs ) {
262 /* get and return value */
263 value = (unsigned short *)( pers->bufptr + pers->cofs );
265 return _pico_little_short( *value );
268 static float GetFloat( T3dsLoaderPers *pers ){
272 if ( pers->cofs > pers->maxofs ) {
276 /* get and return value */
277 value = (float *)( pers->bufptr + pers->cofs );
279 return _pico_little_float( *value );
282 static int GetMeshVertices( T3dsLoaderPers *pers ){
286 /* get number of verts for this surface */
287 numVerts = GetWord( pers );
290 printf( "GetMeshVertices: numverts %d\n",numVerts );
292 /* read in vertices for current surface */
293 for ( i = 0; i < numVerts; i++ )
296 v[0] = GetFloat( pers );
297 v[1] = GetFloat( pers ); /* ydnar: unflipped */
298 v[2] = GetFloat( pers ); /* ydnar: unflipped and negated */
300 /* add current vertex */
301 PicoSetSurfaceXYZ( pers->surface,i,v );
302 PicoSetSurfaceColor( pers->surface,0,i,white ); /* ydnar */
304 #ifdef DEBUG_PM_3DS_EX
305 printf( "Vertex: x: %f y: %f z: %f\n",v[0],v[1],v[2] );
308 /* success (no errors occured) */
312 static int GetMeshFaces( T3dsLoaderPers *pers ){
316 /* get number of faces for this surface */
317 numFaces = GetWord( pers );
320 printf( "GetMeshFaces: numfaces %d\n",numFaces );
322 /* read in vertex indices for current surface */
323 for ( i = 0; i < numFaces; i++ )
325 /* remember, we only need 3 of 4 values read in for each */
326 /* face. the 4th value is a vis flag for 3dsmax which is */
327 /* being ignored by us here */
329 face.a = GetWord( pers );
330 face.c = GetWord( pers ); /* ydnar: flipped order */
331 face.b = GetWord( pers ); /* ydnar: flipped order */
332 face.visible = GetWord( pers );
335 PicoSetSurfaceIndex( pers->surface, ( i * 3 + 0 ), (picoIndex_t)face.a );
336 PicoSetSurfaceIndex( pers->surface, ( i * 3 + 1 ), (picoIndex_t)face.b );
337 PicoSetSurfaceIndex( pers->surface, ( i * 3 + 2 ), (picoIndex_t)face.c );
339 #ifdef DEBUG_PM_3DS_EX
340 printf( "Face: a: %d b: %d c: %d (%d)\n",face.a,face.b,face.c,face.visible );
343 /* success (no errors occured) */
347 static int GetMeshTexCoords( T3dsLoaderPers *pers ){
351 /* get number of uv coords for this surface */
352 numTexCoords = GetWord( pers );
355 printf( "GetMeshTexCoords: numcoords %d\n",numTexCoords );
357 /* read in uv coords for current surface */
358 for ( i = 0; i < numTexCoords; i++ )
361 uv[0] = GetFloat( pers );
362 uv[1] = -GetFloat( pers ); /* ydnar: we use origin at bottom */
364 /* to make sure we don't mess up memory */
365 if ( pers->surface == NULL ) {
370 PicoSetSurfaceST( pers->surface,0,i,uv );
372 #ifdef DEBUG_PM_3DS_EX
373 printf( "u: %f v: %f\n",uv[0],uv[1] );
376 /* success (no errors occured) */
380 static int GetMeshShader( T3dsLoaderPers *pers ){
381 char shaderName[255] = { 0 };
382 picoShader_t *shader;
384 int setShaderName = 0;
387 /* the shader is either the color or the texture map of the */
388 /* object. it can also hold other information like the brightness, */
389 /* shine, etc. stuff we don't really care about. we just want the */
390 /* color, or the texture map file name really */
392 /* get in the shader name */
393 if ( !GetASCIIZ( pers,shaderName,sizeof( shaderName ) ) ) {
397 /* ydnar: trim to first whitespace */
398 _pico_first_token( shaderName );
400 /* now that we have the shader name we need to go through all of */
401 /* the shaders and check the name against each shader. when we */
402 /* find a shader in our shader list that matches this name we */
403 /* just read in, then we assign the shader's id of the object to */
406 /* get shader id for shader name */
407 shader = PicoFindShader( pers->model, shaderName, 1 );
409 /* we've found a matching shader */
410 if ( ( shader != NULL ) && pers->surface ) {
411 char mapName[1024 + 1];
413 memset( mapName,0,sizeof( mapName ) );
415 /* get ptr to shader's map name */
416 mapNamePtr = PicoGetShaderMapName( shader );
418 /* we have a valid map name ptr */
419 if ( mapNamePtr != NULL ) {
423 /* copy map name to local buffer */
424 strcpy( mapName,mapNamePtr );
426 /* extract file name */
427 name = _pico_nopath( mapName );
428 strncpy( temp, name, sizeof( temp ) );
430 /* remove file extension */
431 /* name = _pico_setfext( name,"" ); */
433 /* assign default name if no name available */
434 if ( strlen( temp ) < 1 ) {
435 strcpy( temp,pers->basename );
438 /* build shader name */
439 _pico_strlwr( temp ); /* gaynux update -sea */
440 sprintf( mapName,"models/mapobjects/%s/%s",pers->basename,temp );
442 /* set shader name */
443 /* PicoSetShaderName( shader,mapName ); */ /* ydnar: this will screw up the named shader */
445 /* set surface's shader index */
446 PicoSetSurfaceShader( pers->surface, shader );
451 /* we didn't set a shader name; throw out warning */
452 if ( !setShaderName ) {
453 _pico_printf( PICO_WARNING,"3DS mesh is missing shader name" );
455 /* we don't process the list of shared vertices here; there is a */
456 /* short int that gives the number of faces of the mesh concerned */
457 /* by this shader, then there is the list itself of these faces. */
458 /* 0000 means the first face of the (4120) face list */
460 /* get number of shared verts */
461 numSharedVerts = GetWord( pers );
464 printf( "GetMeshShader: uses shader '%s' (nsv %d)\n",shaderName,numSharedVerts );
466 /* skip list of shared verts */
467 for ( i = 0; i < numSharedVerts; i++ )
471 /* success (no errors occured) */
475 static int GetDiffuseColor( T3dsLoaderPers *pers ){
476 /* todo: support all 3ds specific color formats; */
477 /* that means: rgb,tru,trug,rgbg */
479 /* get rgb color (range 0..255; 3 bytes) */
482 color[0] = GetByte( pers );
483 color[1] = GetByte( pers );
484 color[2] = GetByte( pers );
487 /* store this as the current shader's diffuse color */
488 if ( pers->shader ) {
489 PicoSetShaderDiffuseColor( pers->shader,color );
492 printf( "GetDiffuseColor: %d %d %d\n",color[0],color[1],color[2] );
494 /* success (no errors occured) */
498 static int DoNextEditorDataChunk( T3dsLoaderPers *pers, long endofs ){
501 #ifdef DEBUG_PM_3DS_EX
502 printf( "DoNextEditorDataChunk: endofs %d\n",endofs );
504 while ( pers->cofs < endofs )
506 long nextofs = pers->cofs;
507 if ( ( chunk = GetChunk( pers ) ) == NULL ) {
513 nextofs += chunk->len;
515 #ifdef DEBUG_PM_3DS_EX
516 printf( "Chunk %04x (%s), len %d pers->cofs %x\n",chunk->id,DebugGetChunkName( chunk->id ),chunk->len,pers->cofs );
519 if ( chunk->id == CHUNK_OBJECT ) {
520 picoSurface_t *surface;
521 char surfaceName[ 0xff ] = { 0 };
523 /* read in surface name */
524 if ( !GetASCIIZ( pers,surfaceName,sizeof( surfaceName ) ) ) {
525 return 0; /* this is bad */
529 /* ignore NULL name surfaces */
532 /* allocate a pico surface */
533 surface = PicoNewSurface( pers->model );
534 if ( surface == NULL ) {
535 pers->surface = NULL;
536 return 0; /* this is bad too */
538 /* assign ptr to current surface */
539 pers->surface = surface;
541 /* 3ds models surfaces are all triangle meshes */
542 PicoSetSurfaceType( pers->surface,PICO_TRIANGLES );
544 /* set surface name */
545 PicoSetSurfaceName( pers->surface,surfaceName );
547 /* continue mess with object's sub chunks */
548 DoNextEditorDataChunk( pers,nextofs );
551 if ( chunk->id == CHUNK_OBJECT_MESH ) {
552 /* continue mess with mesh's sub chunks */
553 if ( !DoNextEditorDataChunk( pers,nextofs ) ) {
558 if ( chunk->id == CHUNK_OBJECT_VERTICES ) {
559 if ( !GetMeshVertices( pers ) ) {
564 if ( chunk->id == CHUNK_OBJECT_FACES ) {
565 if ( !GetMeshFaces( pers ) ) {
570 if ( chunk->id == CHUNK_OBJECT_UV ) {
571 if ( !GetMeshTexCoords( pers ) ) {
576 if ( chunk->id == CHUNK_OBJECT_MATERIAL ) {
577 if ( !GetMeshShader( pers ) ) {
583 if ( chunk->id == CHUNK_MATERIAL ) {
584 /* new shader specific things should be */
585 /* initialized right here */
586 picoShader_t *shader;
588 /* allocate a pico shader */
589 shader = PicoNewShader( pers->model ); /* ydnar */
590 if ( shader == NULL ) {
592 return 0; /* this is bad too */
595 /* assign ptr to current shader */
596 pers->shader = shader;
598 /* continue and process the material's sub chunks */
599 DoNextEditorDataChunk( pers,nextofs );
602 if ( chunk->id == CHUNK_MATNAME ) {
603 /* new material's names should be stored here. note that */
604 /* GetMeshMaterial returns the name of the material that */
605 /* is used by the mesh. new material names are set HERE. */
606 /* but for now we skip the new material's name ... */
607 if ( pers->shader ) {
608 char *name = (char*) ( pers->bufptr + pers->cofs );
609 char *cleanedName = _pico_clone_alloc( name );
610 _pico_first_token( cleanedName );
611 PicoSetShaderName( pers->shader, cleanedName );
613 printf( "NewShader: '%s'\n", cleanedName );
615 _pico_free( cleanedName );
618 if ( chunk->id == CHUNK_MATDIFFUSE ) {
619 /* todo: color for last inserted new material should be */
620 /* stored somewhere by GetDiffuseColor */
621 if ( !GetDiffuseColor( pers ) ) {
625 /* rest of chunk is skipped here */
627 if ( chunk->id == CHUNK_MATMAP ) {
628 /* continue and process the material map sub chunks */
629 DoNextEditorDataChunk( pers,nextofs );
632 if ( chunk->id == CHUNK_MATMAPFILE ) {
633 /* map file name for last inserted new material should */
634 /* be stored here. but for now we skip this too ... */
635 if ( pers->shader ) {
636 char *name = (char *)( pers->bufptr + pers->cofs );
637 PicoSetShaderMapName( pers->shader,name );
639 printf( "NewShaderMapfile: '%s'\n",name );
644 if ( chunk->id == CHUNK_KEYFRAME_DATA ) {
645 /* well umm, this is a bit too much since we don't really */
646 /* need model animation sequences right now. we skip this */
648 printf( "KeyframeData: len %d\n",chunk->len );
651 /* skip unknown chunk */
652 pers->cofs = nextofs;
653 if ( pers->cofs >= pers->maxofs ) {
660 static int DoNextChunk( T3dsLoaderPers *pers, int endofs ){
664 printf( "DoNextChunk: endofs %d\n",endofs );
666 while ( pers->cofs < endofs )
668 long nextofs = pers->cofs;
669 if ( ( chunk = GetChunk( pers ) ) == NULL ) {
675 nextofs += chunk->len;
677 #ifdef DEBUG_PM_3DS_EX
678 printf( "Chunk %04x (%s), len %d pers->cofs %x\n",chunk->id,DebugGetChunkName( chunk->id ),chunk->len,pers->cofs );
681 if ( chunk->id == CHUNK_VERSION ) {
682 /* at this point i get the 3ds file version. since there */
683 /* might be new additions to the 3ds file format in 4.0 */
684 /* it might be a good idea to store the version somewhere */
685 /* for later handling or message displaying */
687 /* get the version */
689 version = GetWord( pers );
692 printf( "FileVersion: %d\n",version );
695 /* throw out a warning for version 4 models */
696 if ( version == 4 ) {
697 _pico_printf( PICO_WARNING,
698 "3DS version is 4. Model might load incorrectly." );
700 /* store the 3ds file version in pico special field 0 */
701 /* PicoSetSurfaceSpecial(pers->surface,0,version); */ /* ydnar: this was causing a crash accessing uninitialized surface */
703 /* rest of chunk is skipped here */
705 /*** editor data ***/
706 if ( chunk->id == CHUNK_EDITOR_DATA ) {
707 if ( !DoNextEditorDataChunk( pers,nextofs ) ) {
712 /* skip unknown chunk */
713 pers->cofs = nextofs;
714 if ( pers->cofs >= pers->maxofs ) {
722 * loads an autodesk 3ds model file.
724 static picoModel_t *_3ds_load( PM_PARAMS_LOAD ){
729 /* create a new pico model */
730 model = PicoNewModel();
731 if ( model == NULL ) {
732 /* user must have some serious ram problems ;) */
735 /* get model's base name (eg. jeep from c:\models\jeep.3ds) */
736 memset( basename,0,sizeof( basename ) );
737 strncpy( basename,_pico_nopath( fileName ),sizeof( basename ) );
738 _pico_setfext( basename,"" );
740 /* initialize persistant vars (formerly static) */
742 pers.bufptr = (picoByte_t *)_pico_alloc( bufSize );
743 memcpy( pers.bufptr, buffer, bufSize );
744 pers.basename = (char *)basename;
745 pers.maxofs = bufSize;
749 PicoSetModelFrameNum( model,frameNum );
750 PicoSetModelName( model,fileName );
751 PicoSetModelFileName( model,fileName );
753 /* skip first chunk in file (magic) */
757 if ( !DoNextChunk( &pers,pers.maxofs ) ) {
758 /* well, bleh i guess */
759 PicoFreeModel( model );
762 /* return allocated pico model */
766 /* pico file format module definition */
767 const picoModule_t picoModule3DS =
769 "0.86-b", /* module version string */
770 "Autodesk 3Dstudio", /* module display name */
771 "seaw0lf", /* author's name */
772 "2002 seaw0lf", /* module copyright */
774 "3ds",NULL,NULL,NULL /* default extensions to use */
776 _3ds_canload, /* validation routine */
777 _3ds_load, /* load routine */
778 NULL, /* save validation routine */
779 NULL /* save routine */