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 ----------------------------------------------------------------------------- */
36 #include "picointernal.h"
39 * - 3ds file version is stored in pico special field 0 on load (ydnar: removed)
41 * - sometimes there is one unnamed surface 0 having 0 verts as
42 * well as 0 faces. this error occurs since pm 0.6 (ydnar?)
44 /* uncomment when debugging this module */
45 /* #define DEBUG_PM_3DS
46 #define DEBUG_PM_3DS_EX */
48 /* structure holding persistent 3ds loader specific data used */
49 /* to store formerly static vars to keep the module reentrant */
50 /* safe. put everything that needs to be static in here. */
51 typedef struct S3dsLoaderPers
53 picoModel_t *model; /* ptr to output model */
54 picoSurface_t *surface; /* ptr to current surface */
55 picoShader_t *shader; /* ptr to current shader */
56 picoByte_t *bufptr; /* ptr to raw data */
57 char *basename; /* ptr to model base name (eg. jeep) */
63 /* 3ds chunk types that we use */
69 CHUNK_VERSION = 0x0002,
70 CHUNK_EDITOR_CONFIG = 0x3D3E,
71 CHUNK_EDITOR_DATA = 0x3D3D,
72 CHUNK_KEYFRAME_DATA = 0xB000,
74 /* editor data sub chunks */
75 CHUNK_MATERIAL = 0xAFFF,
76 CHUNK_OBJECT = 0x4000,
78 /* material sub chunks */
79 CHUNK_MATNAME = 0xA000,
80 CHUNK_MATDIFFUSE = 0xA020,
81 CHUNK_MATMAP = 0xA200,
82 CHUNK_MATMAPFILE = 0xA300,
84 /* lets us know we're reading a new object */
85 CHUNK_OBJECT_MESH = 0x4100,
87 /* object mesh sub chunks */
88 CHUNK_OBJECT_VERTICES = 0x4110,
89 CHUNK_OBJECT_FACES = 0x4120,
90 CHUNK_OBJECT_MATERIAL = 0x4130,
91 CHUNK_OBJECT_UV = 0x4140,
101 { CHUNK_MAIN, "CHUNK_MAIN" },
102 { CHUNK_VERSION, "CHUNK_VERSION" },
103 { CHUNK_EDITOR_CONFIG, "CHUNK_EDITOR_CONFIG" },
104 { CHUNK_EDITOR_DATA, "CHUNK_EDITOR_DATA" },
105 { CHUNK_KEYFRAME_DATA, "CHUNK_KEYFRAME_DATA" },
106 { CHUNK_MATERIAL, "CHUNK_MATERIAL" },
107 { CHUNK_OBJECT, "CHUNK_OBJECT" },
108 { CHUNK_MATNAME, "CHUNK_MATNAME" },
109 { CHUNK_MATDIFFUSE, "CHUNK_MATDIFFUSE" },
110 { CHUNK_MATMAP, "CHUNK_MATMAP" },
111 { CHUNK_MATMAPFILE, "CHUNK_MATMAPFILE" },
112 { CHUNK_OBJECT_MESH, "CHUNK_OBJECT_MESH" },
113 { CHUNK_OBJECT_VERTICES, "CHUNK_OBJECT_VERTICES" },
114 { CHUNK_OBJECT_FACES, "CHUNK_OBJECT_FACES" },
115 { CHUNK_OBJECT_MATERIAL, "CHUNK_OBJECT_MATERIAL" },
116 { CHUNK_OBJECT_UV, "CHUNK_OBJECT_UV" },
119 static char *DebugGetChunkName( int id ) {
120 int i,max; /* imax? ;) */
121 max = sizeof( debugChunkNames ) / sizeof( debugChunkNames[0] );
123 for ( i = 0; i < max; i++ )
125 if ( debugChunkNames[i].id == id ) {
126 /* gaynux update -sea */
127 return _pico_strlwr( debugChunkNames[i].name );
130 return "chunk_unknown";
132 #endif /*DEBUG_PM_3DS*/
134 /* this funky loader needs byte alignment */
135 #pragma pack(push, 1)
137 typedef struct S3dsIndices
139 unsigned short a,b,c;
140 unsigned short visible;
144 typedef struct S3dsChunk
151 /* restore previous data alignment */
155 * validates an autodesk 3ds model file.
157 static int _3ds_canload( PM_PARAMS_CANLOAD ){
158 const T3dsChunk *chunk;
161 if ( bufSize < (int) sizeof( T3dsChunk ) ) {
162 return PICO_PMV_ERROR_SIZE;
165 /* get pointer to 3ds header chunk */
166 chunk = (const T3dsChunk *)buffer;
168 /* check data length */
169 if ( bufSize < (int) _pico_little_long( chunk->len ) ) {
170 return PICO_PMV_ERROR_SIZE;
173 /* check 3ds magic */
174 if ( _pico_little_short( chunk->id ) != CHUNK_MAIN ) {
175 return PICO_PMV_ERROR_IDENT;
178 /* file seems to be a valid 3ds */
182 static T3dsChunk *GetChunk( T3dsLoaderPers *pers ){
186 if ( pers->cofs > pers->maxofs ) {
191 /* printf("GetChunk: pers->cofs %x\n",pers->cofs); */
193 /* fill in pointer to chunk */
194 chunk = (T3dsChunk *)&pers->bufptr[ pers->cofs ];
199 chunk->id = _pico_little_short( chunk->id );
200 chunk->len = _pico_little_long( chunk->len );
202 /* advance in buffer */
203 pers->cofs += sizeof( T3dsChunk );
209 static int GetASCIIZ( T3dsLoaderPers *pers, char *dest, int max ){
215 ch = pers->bufptr[ pers->cofs++ ];
219 if ( pers->cofs >= pers->maxofs ) {
232 static picoByte_t GetByte( T3dsLoaderPers *pers ){
236 if ( pers->cofs > pers->maxofs ) {
240 /* get and return value */
241 value = (picoByte_t *)( pers->bufptr + pers->cofs );
246 static int GetWord( T3dsLoaderPers *pers ){
247 unsigned short *value;
250 if ( pers->cofs > pers->maxofs ) {
254 /* get and return value */
255 value = (unsigned short *)( pers->bufptr + pers->cofs );
257 return _pico_little_short( *value );
260 static float GetFloat( T3dsLoaderPers *pers ){
264 if ( pers->cofs > pers->maxofs ) {
268 /* get and return value */
269 value = (float *)( pers->bufptr + pers->cofs );
271 return _pico_little_float( *value );
274 static int GetMeshVertices( T3dsLoaderPers *pers ){
278 /* get number of verts for this surface */
279 numVerts = GetWord( pers );
282 printf( "GetMeshVertices: numverts %d\n",numVerts );
284 /* read in vertices for current surface */
285 for ( i = 0; i < numVerts; i++ )
288 v[0] = GetFloat( pers );
289 v[1] = GetFloat( pers ); /* ydnar: unflipped */
290 v[2] = GetFloat( pers ); /* ydnar: unflipped and negated */
292 /* add current vertex */
293 PicoSetSurfaceXYZ( pers->surface,i,v );
294 PicoSetSurfaceColor( pers->surface, 0, i, picoColor_white );
296 #ifdef DEBUG_PM_3DS_EX
297 printf( "Vertex: x: %f y: %f z: %f\n",v[0],v[1],v[2] );
300 /* success (no errors occured) */
304 static int GetMeshFaces( T3dsLoaderPers *pers ){
308 /* get number of faces for this surface */
309 numFaces = GetWord( pers );
312 printf( "GetMeshFaces: numfaces %d\n",numFaces );
314 /* read in vertex indices for current surface */
315 for ( i = 0; i < numFaces; i++ )
317 /* remember, we only need 3 of 4 values read in for each */
318 /* face. the 4th value is a vis flag for 3dsmax which is */
319 /* being ignored by us here */
321 face.a = GetWord( pers );
322 face.c = GetWord( pers ); /* ydnar: flipped order */
323 face.b = GetWord( pers ); /* ydnar: flipped order */
324 face.visible = GetWord( pers );
327 PicoSetSurfaceIndex( pers->surface, ( i * 3 + 0 ), (picoIndex_t)face.a );
328 PicoSetSurfaceIndex( pers->surface, ( i * 3 + 1 ), (picoIndex_t)face.b );
329 PicoSetSurfaceIndex( pers->surface, ( i * 3 + 2 ), (picoIndex_t)face.c );
331 #ifdef DEBUG_PM_3DS_EX
332 printf( "Face: a: %d b: %d c: %d (%d)\n",face.a,face.b,face.c,face.visible );
335 /* success (no errors occured) */
339 static int GetMeshTexCoords( T3dsLoaderPers *pers ){
343 /* get number of uv coords for this surface */
344 numTexCoords = GetWord( pers );
347 printf( "GetMeshTexCoords: numcoords %d\n",numTexCoords );
349 /* read in uv coords for current surface */
350 for ( i = 0; i < numTexCoords; i++ )
353 uv[0] = GetFloat( pers );
354 uv[1] = -GetFloat( pers ); /* ydnar: we use origin at bottom */
356 /* to make sure we don't mess up memory */
357 if ( pers->surface == NULL ) {
362 PicoSetSurfaceST( pers->surface,0,i,uv );
364 #ifdef DEBUG_PM_3DS_EX
365 printf( "u: %f v: %f\n",uv[0],uv[1] );
368 /* success (no errors occured) */
372 static int GetMeshShader( T3dsLoaderPers *pers ){
373 char shaderName[255] = { 0 };
374 picoShader_t *shader;
376 int setShaderName = 0;
379 /* the shader is either the color or the texture map of the */
380 /* object. it can also hold other information like the brightness, */
381 /* shine, etc. stuff we don't really care about. we just want the */
382 /* color, or the texture map file name really */
384 /* get in the shader name */
385 if ( !GetASCIIZ( pers,shaderName,sizeof( shaderName ) ) ) {
389 /* ydnar: trim to first whitespace */
390 _pico_first_token( shaderName );
392 /* now that we have the shader name we need to go through all of */
393 /* the shaders and check the name against each shader. when we */
394 /* find a shader in our shader list that matches this name we */
395 /* just read in, then we assign the shader's id of the object to */
398 /* get shader id for shader name */
399 shader = PicoFindShader( pers->model, shaderName, 1 );
401 /* we've found a matching shader */
402 if ( ( shader != NULL ) && pers->surface ) {
403 /* get ptr to shader's map name */
404 const char* mapNamePtr = PicoGetShaderMapName( shader );
405 /* we have a valid map name ptr */
406 if ( mapNamePtr != NULL ) {
410 char mapName[1024 + 1];
411 memset( mapName,0,sizeof( mapName ) );
413 /* copy map name to local buffer */
414 strcpy( mapName,mapNamePtr );
416 /* extract file name */
417 name = _pico_nopath( mapName );
418 strncpy( temp, name, sizeof( temp ) );
420 /* remove file extension */
421 /* name = _pico_setfext( name, NULL ); */
423 /* assign default name if no name available */
424 if ( strlen( temp ) < 1 ) {
425 strcpy( temp,pers->basename );
428 /* build shader name */
429 _pico_strlwr( temp ); /* gaynux update -sea */
430 sprintf( mapName,"models/mapobjects/%s/%s",pers->basename,temp );
432 /* set shader name */
433 /* PicoSetShaderName( shader,mapName ); */ /* ydnar: this will screw up the named shader */
435 /* set surface's shader index */
436 PicoSetSurfaceShader( pers->surface, shader );
441 /* we didn't set a shader name; throw out warning */
442 if ( !setShaderName ) {
443 _pico_printf( PICO_WARNING,"3DS mesh is missing shader name" );
445 /* we don't process the list of shared vertices here; there is a */
446 /* short int that gives the number of faces of the mesh concerned */
447 /* by this shader, then there is the list itself of these faces. */
448 /* 0000 means the first face of the (4120) face list */
450 /* get number of shared verts */
451 numSharedVerts = GetWord( pers );
454 printf( "GetMeshShader: uses shader '%s' (nsv %d)\n",shaderName,numSharedVerts );
456 /* skip list of shared verts */
457 for ( i = 0; i < numSharedVerts; i++ )
461 /* success (no errors occured) */
465 static int GetDiffuseColor( T3dsLoaderPers *pers ){
466 /* todo: support all 3ds specific color formats; */
467 /* that means: rgb,tru,trug,rgbg */
469 /* get rgb color (range 0..255; 3 bytes) */
472 color[0] = GetByte( pers );
473 color[1] = GetByte( pers );
474 color[2] = GetByte( pers );
477 /* store this as the current shader's diffuse color */
478 if ( pers->shader ) {
479 PicoSetShaderDiffuseColor( pers->shader,color );
482 printf( "GetDiffuseColor: %d %d %d\n",color[0],color[1],color[2] );
484 /* success (no errors occured) */
488 static int DoNextEditorDataChunk( T3dsLoaderPers *pers, long endofs ){
491 #ifdef DEBUG_PM_3DS_EX
492 printf( "DoNextEditorDataChunk: endofs %d\n",endofs );
494 while ( pers->cofs < endofs )
496 long nextofs = pers->cofs;
497 if ( ( chunk = GetChunk( pers ) ) == NULL ) {
503 nextofs += chunk->len;
505 #ifdef DEBUG_PM_3DS_EX
506 printf( "Chunk %04x (%s), len %d pers->cofs %x\n",chunk->id,DebugGetChunkName( chunk->id ),chunk->len,pers->cofs );
509 if ( chunk->id == CHUNK_OBJECT ) {
510 picoSurface_t *surface;
511 char surfaceName[ 0xff ] = { 0 };
513 /* read in surface name */
514 if ( !GetASCIIZ( pers,surfaceName,sizeof( surfaceName ) ) ) {
515 return 0; /* this is bad */
518 /* ignore NULL name surfaces */
521 /* allocate a pico surface */
522 surface = PicoNewSurface( pers->model );
523 if ( surface == NULL ) {
524 pers->surface = NULL;
525 return 0; /* this is bad too */
527 /* assign ptr to current surface */
528 pers->surface = surface;
530 /* 3ds models surfaces are all triangle meshes */
531 PicoSetSurfaceType( pers->surface,PICO_TRIANGLES );
533 /* set surface name */
534 PicoSetSurfaceName( pers->surface,surfaceName );
536 /* continue mess with object's sub chunks */
537 DoNextEditorDataChunk( pers,nextofs );
540 if ( chunk->id == CHUNK_OBJECT_MESH ) {
541 /* continue mess with mesh's sub chunks */
542 if ( !DoNextEditorDataChunk( pers,nextofs ) ) {
547 if ( chunk->id == CHUNK_OBJECT_VERTICES ) {
548 if ( !GetMeshVertices( pers ) ) {
553 if ( chunk->id == CHUNK_OBJECT_FACES ) {
554 if ( !GetMeshFaces( pers ) ) {
559 if ( chunk->id == CHUNK_OBJECT_UV ) {
560 if ( !GetMeshTexCoords( pers ) ) {
565 if ( chunk->id == CHUNK_OBJECT_MATERIAL ) {
566 if ( !GetMeshShader( pers ) ) {
572 if ( chunk->id == CHUNK_MATERIAL ) {
573 /* new shader specific things should be */
574 /* initialized right here */
575 picoShader_t *shader;
577 /* allocate a pico shader */
578 shader = PicoNewShader( pers->model ); /* ydnar */
579 if ( shader == NULL ) {
581 return 0; /* this is bad too */
584 /* assign ptr to current shader */
585 pers->shader = shader;
587 /* continue and process the material's sub chunks */
588 DoNextEditorDataChunk( pers,nextofs );
591 if ( chunk->id == CHUNK_MATNAME ) {
592 /* new material's names should be stored here. note that */
593 /* GetMeshMaterial returns the name of the material that */
594 /* is used by the mesh. new material names are set HERE. */
595 /* but for now we skip the new material's name ... */
596 if ( pers->shader ) {
597 char *name = (char*) ( pers->bufptr + pers->cofs );
598 char *cleanedName = _pico_clone_alloc( name );
599 _pico_first_token( cleanedName );
600 PicoSetShaderName( pers->shader, cleanedName );
602 printf( "NewShader: '%s'\n", cleanedName );
604 _pico_free( cleanedName );
607 if ( chunk->id == CHUNK_MATDIFFUSE ) {
608 /* todo: color for last inserted new material should be */
609 /* stored somewhere by GetDiffuseColor */
610 if ( !GetDiffuseColor( pers ) ) {
614 /* rest of chunk is skipped here */
616 if ( chunk->id == CHUNK_MATMAP ) {
617 /* continue and process the material map sub chunks */
618 DoNextEditorDataChunk( pers,nextofs );
621 if ( chunk->id == CHUNK_MATMAPFILE ) {
622 /* map file name for last inserted new material should */
623 /* be stored here. but for now we skip this too ... */
624 if ( pers->shader ) {
625 char *name = (char *)( pers->bufptr + pers->cofs );
626 PicoSetShaderMapName( pers->shader,name );
628 printf( "NewShaderMapfile: '%s'\n",name );
633 if ( chunk->id == CHUNK_KEYFRAME_DATA ) {
634 /* well umm, this is a bit too much since we don't really */
635 /* need model animation sequences right now. we skip this */
637 printf( "KeyframeData: len %d\n",chunk->len );
640 /* skip unknown chunk */
641 pers->cofs = nextofs;
642 if ( pers->cofs >= pers->maxofs ) {
649 static int DoNextChunk( T3dsLoaderPers *pers, int endofs ){
653 printf( "DoNextChunk: endofs %d\n",endofs );
655 while ( pers->cofs < endofs )
657 long nextofs = pers->cofs;
658 if ( ( chunk = GetChunk( pers ) ) == NULL ) {
664 nextofs += chunk->len;
666 #ifdef DEBUG_PM_3DS_EX
667 printf( "Chunk %04x (%s), len %d pers->cofs %x\n",chunk->id,DebugGetChunkName( chunk->id ),chunk->len,pers->cofs );
670 if ( chunk->id == CHUNK_VERSION ) {
671 /* at this point i get the 3ds file version. since there */
672 /* might be new additions to the 3ds file format in 4.0 */
673 /* it might be a good idea to store the version somewhere */
674 /* for later handling or message displaying */
676 /* get the version */
678 version = GetWord( pers );
681 printf( "FileVersion: %d\n",version );
684 /* throw out a warning for version 4 models */
685 if ( version == 4 ) {
686 _pico_printf( PICO_WARNING,
687 "3DS version is 4. Model might load incorrectly." );
689 /* store the 3ds file version in pico special field 0 */
690 /* PicoSetSurfaceSpecial(pers->surface,0,version); */ /* ydnar: this was causing a crash accessing uninitialized surface */
692 /* rest of chunk is skipped here */
694 /*** editor data ***/
695 if ( chunk->id == CHUNK_EDITOR_DATA ) {
696 if ( !DoNextEditorDataChunk( pers,nextofs ) ) {
701 /* skip unknown chunk */
702 pers->cofs = nextofs;
703 if ( pers->cofs >= pers->maxofs ) {
711 * loads an autodesk 3ds model file.
713 static picoModel_t *_3ds_load( PM_PARAMS_LOAD ){
718 /* create a new pico model */
719 model = PicoNewModel();
720 if ( model == NULL ) {
721 /* user must have some serious ram problems ;) */
724 /* get model's base name (eg. jeep from c:\models\jeep.3ds) */
725 memset( basename, 0, sizeof( basename ) );
726 strncpy( basename, _pico_nopath( fileName ), sizeof( basename ) - 1 );
727 _pico_setfext( basename, NULL );
729 /* initialize persistant vars (formerly static) */
731 pers.bufptr = (picoByte_t *)_pico_alloc( bufSize );
732 memcpy( pers.bufptr, buffer, bufSize );
733 pers.basename = (char *)basename;
734 pers.maxofs = bufSize;
738 PicoSetModelFrameNum( model,frameNum );
739 PicoSetModelName( model,fileName );
740 PicoSetModelFileName( model,fileName );
742 /* skip first chunk in file (magic) */
746 if ( !DoNextChunk( &pers,pers.maxofs ) ) {
747 /* well, bleh i guess */
748 PicoFreeModel( model );
751 /* return allocated pico model */
755 /* pico file format module definition */
756 const picoModule_t picoModule3DS =
758 "0.86-b", /* module version string */
759 "Autodesk 3Dstudio", /* module display name */
760 "seaw0lf", /* author's name */
761 "2002 seaw0lf", /* module copyright */
763 "3ds",NULL,NULL,NULL /* default extensions to use */
765 _3ds_canload, /* validation routine */
766 _3ds_load, /* load routine */
767 NULL, /* save validation routine */
768 NULL /* save routine */