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 static picoColor_t white = { 255,255,255,255 };
42 * - 3ds file version is stored in pico special field 0 on load (ydnar: removed)
44 * - sometimes there is one unnamed surface 0 having 0 verts as
45 * well as 0 faces. this error occurs since pm 0.6 (ydnar?)
47 /* uncomment when debugging this module */
48 /* #define DEBUG_PM_3DS
49 #define DEBUG_PM_3DS_EX */
51 /* structure holding persistent 3ds loader specific data used */
52 /* to store formerly static vars to keep the module reentrant */
53 /* safe. put everything that needs to be static in here. */
54 typedef struct S3dsLoaderPers
56 picoModel_t *model; /* ptr to output model */
57 picoSurface_t *surface; /* ptr to current surface */
58 picoShader_t *shader; /* ptr to current shader */
59 picoByte_t *bufptr; /* ptr to raw data */
60 char *basename; /* ptr to model base name (eg. jeep) */
66 /* 3ds chunk types that we use */
72 CHUNK_VERSION = 0x0002,
73 CHUNK_EDITOR_CONFIG = 0x3D3E,
74 CHUNK_EDITOR_DATA = 0x3D3D,
75 CHUNK_KEYFRAME_DATA = 0xB000,
77 /* editor data sub chunks */
78 CHUNK_MATERIAL = 0xAFFF,
79 CHUNK_OBJECT = 0x4000,
81 /* material sub chunks */
82 CHUNK_MATNAME = 0xA000,
83 CHUNK_MATDIFFUSE = 0xA020,
84 CHUNK_MATMAP = 0xA200,
85 CHUNK_MATMAPFILE = 0xA300,
87 /* lets us know we're reading a new object */
88 CHUNK_OBJECT_MESH = 0x4100,
90 /* object mesh sub chunks */
91 CHUNK_OBJECT_VERTICES = 0x4110,
92 CHUNK_OBJECT_FACES = 0x4120,
93 CHUNK_OBJECT_MATERIAL = 0x4130,
94 CHUNK_OBJECT_UV = 0x4140,
104 { CHUNK_MAIN, "CHUNK_MAIN" },
105 { CHUNK_VERSION, "CHUNK_VERSION" },
106 { CHUNK_EDITOR_CONFIG, "CHUNK_EDITOR_CONFIG" },
107 { CHUNK_EDITOR_DATA, "CHUNK_EDITOR_DATA" },
108 { CHUNK_KEYFRAME_DATA, "CHUNK_KEYFRAME_DATA" },
109 { CHUNK_MATERIAL, "CHUNK_MATERIAL" },
110 { CHUNK_OBJECT, "CHUNK_OBJECT" },
111 { CHUNK_MATNAME, "CHUNK_MATNAME" },
112 { CHUNK_MATDIFFUSE, "CHUNK_MATDIFFUSE" },
113 { CHUNK_MATMAP, "CHUNK_MATMAP" },
114 { CHUNK_MATMAPFILE, "CHUNK_MATMAPFILE" },
115 { CHUNK_OBJECT_MESH, "CHUNK_OBJECT_MESH" },
116 { CHUNK_OBJECT_VERTICES, "CHUNK_OBJECT_VERTICES" },
117 { CHUNK_OBJECT_FACES, "CHUNK_OBJECT_FACES" },
118 { CHUNK_OBJECT_MATERIAL, "CHUNK_OBJECT_MATERIAL" },
119 { CHUNK_OBJECT_UV, "CHUNK_OBJECT_UV" },
122 static char *DebugGetChunkName( int id ) {
123 int i,max; /* imax? ;) */
124 max = sizeof( debugChunkNames ) / sizeof( debugChunkNames[0] );
126 for ( i = 0; i < max; i++ )
128 if ( debugChunkNames[i].id == id ) {
129 /* gaynux update -sea */
130 return _pico_strlwr( debugChunkNames[i].name );
133 return "chunk_unknown";
135 #endif /*DEBUG_PM_3DS*/
137 /* this funky loader needs byte alignment */
138 #pragma pack(push, 1)
140 typedef struct S3dsIndices
142 unsigned short a,b,c;
143 unsigned short visible;
147 typedef struct S3dsChunk
154 /* restore previous data alignment */
158 * validates an autodesk 3ds model file.
160 static int _3ds_canload( PM_PARAMS_CANLOAD ){
161 const T3dsChunk *chunk;
164 if ( bufSize < (int) sizeof( T3dsChunk ) ) {
165 return PICO_PMV_ERROR_SIZE;
168 /* get pointer to 3ds header chunk */
169 chunk = (const T3dsChunk *)buffer;
171 /* check data length */
172 if ( bufSize < (int) _pico_little_long( chunk->len ) ) {
173 return PICO_PMV_ERROR_SIZE;
176 /* check 3ds magic */
177 if ( _pico_little_short( chunk->id ) != CHUNK_MAIN ) {
178 return PICO_PMV_ERROR_IDENT;
181 /* file seems to be a valid 3ds */
185 static T3dsChunk *GetChunk( T3dsLoaderPers *pers ){
189 if ( pers->cofs > pers->maxofs ) {
194 /* printf("GetChunk: pers->cofs %x\n",pers->cofs); */
196 /* fill in pointer to chunk */
197 chunk = (T3dsChunk *)&pers->bufptr[ pers->cofs ];
202 chunk->id = _pico_little_short( chunk->id );
203 chunk->len = _pico_little_long( chunk->len );
205 /* advance in buffer */
206 pers->cofs += sizeof( T3dsChunk );
212 static int GetASCIIZ( T3dsLoaderPers *pers, char *dest, int max ){
218 ch = pers->bufptr[ pers->cofs++ ];
222 if ( pers->cofs >= pers->maxofs ) {
235 static picoByte_t GetByte( T3dsLoaderPers *pers ){
239 if ( pers->cofs > pers->maxofs ) {
243 /* get and return value */
244 value = (picoByte_t *)( pers->bufptr + pers->cofs );
249 static int GetWord( T3dsLoaderPers *pers ){
250 unsigned short *value;
253 if ( pers->cofs > pers->maxofs ) {
257 /* get and return value */
258 value = (unsigned short *)( pers->bufptr + pers->cofs );
260 return _pico_little_short( *value );
263 static float GetFloat( T3dsLoaderPers *pers ){
267 if ( pers->cofs > pers->maxofs ) {
271 /* get and return value */
272 value = (float *)( pers->bufptr + pers->cofs );
274 return _pico_little_float( *value );
277 static int GetMeshVertices( T3dsLoaderPers *pers ){
281 /* get number of verts for this surface */
282 numVerts = GetWord( pers );
285 printf( "GetMeshVertices: numverts %d\n",numVerts );
287 /* read in vertices for current surface */
288 for ( i = 0; i < numVerts; i++ )
291 v[0] = GetFloat( pers );
292 v[1] = GetFloat( pers ); /* ydnar: unflipped */
293 v[2] = GetFloat( pers ); /* ydnar: unflipped and negated */
295 /* add current vertex */
296 PicoSetSurfaceXYZ( pers->surface,i,v );
297 PicoSetSurfaceColor( pers->surface,0,i,white ); /* ydnar */
299 #ifdef DEBUG_PM_3DS_EX
300 printf( "Vertex: x: %f y: %f z: %f\n",v[0],v[1],v[2] );
303 /* success (no errors occured) */
307 static int GetMeshFaces( T3dsLoaderPers *pers ){
311 /* get number of faces for this surface */
312 numFaces = GetWord( pers );
315 printf( "GetMeshFaces: numfaces %d\n",numFaces );
317 /* read in vertex indices for current surface */
318 for ( i = 0; i < numFaces; i++ )
320 /* remember, we only need 3 of 4 values read in for each */
321 /* face. the 4th value is a vis flag for 3dsmax which is */
322 /* being ignored by us here */
324 face.a = GetWord( pers );
325 face.c = GetWord( pers ); /* ydnar: flipped order */
326 face.b = GetWord( pers ); /* ydnar: flipped order */
327 face.visible = GetWord( pers );
330 PicoSetSurfaceIndex( pers->surface, ( i * 3 + 0 ), (picoIndex_t)face.a );
331 PicoSetSurfaceIndex( pers->surface, ( i * 3 + 1 ), (picoIndex_t)face.b );
332 PicoSetSurfaceIndex( pers->surface, ( i * 3 + 2 ), (picoIndex_t)face.c );
334 #ifdef DEBUG_PM_3DS_EX
335 printf( "Face: a: %d b: %d c: %d (%d)\n",face.a,face.b,face.c,face.visible );
338 /* success (no errors occured) */
342 static int GetMeshTexCoords( T3dsLoaderPers *pers ){
346 /* get number of uv coords for this surface */
347 numTexCoords = GetWord( pers );
350 printf( "GetMeshTexCoords: numcoords %d\n",numTexCoords );
352 /* read in uv coords for current surface */
353 for ( i = 0; i < numTexCoords; i++ )
356 uv[0] = GetFloat( pers );
357 uv[1] = -GetFloat( pers ); /* ydnar: we use origin at bottom */
359 /* to make sure we don't mess up memory */
360 if ( pers->surface == NULL ) {
365 PicoSetSurfaceST( pers->surface,0,i,uv );
367 #ifdef DEBUG_PM_3DS_EX
368 printf( "u: %f v: %f\n",uv[0],uv[1] );
371 /* success (no errors occured) */
375 static int GetMeshShader( T3dsLoaderPers *pers ){
376 char shaderName[255] = { 0 };
377 picoShader_t *shader;
379 int setShaderName = 0;
382 /* the shader is either the color or the texture map of the */
383 /* object. it can also hold other information like the brightness, */
384 /* shine, etc. stuff we don't really care about. we just want the */
385 /* color, or the texture map file name really */
387 /* get in the shader name */
388 if ( !GetASCIIZ( pers,shaderName,sizeof( shaderName ) ) ) {
392 /* ydnar: trim to first whitespace */
393 _pico_first_token( shaderName );
395 /* now that we have the shader name we need to go through all of */
396 /* the shaders and check the name against each shader. when we */
397 /* find a shader in our shader list that matches this name we */
398 /* just read in, then we assign the shader's id of the object to */
401 /* get shader id for shader name */
402 shader = PicoFindShader( pers->model, shaderName, 1 );
404 /* we've found a matching shader */
405 if ( ( shader != NULL ) && pers->surface ) {
406 char mapName[1024 + 1];
408 memset( mapName,0,sizeof( mapName ) );
410 /* get ptr to shader's map name */
411 mapNamePtr = PicoGetShaderMapName( shader );
413 /* we have a valid map name ptr */
414 if ( mapNamePtr != NULL ) {
418 /* copy map name to local buffer */
419 strcpy( mapName,mapNamePtr );
421 /* extract file name */
422 name = _pico_nopath( mapName );
423 strncpy( temp, name, sizeof( temp ) );
425 /* remove file extension */
426 /* name = _pico_setfext( name,"" ); */
428 /* assign default name if no name available */
429 if ( strlen( temp ) < 1 ) {
430 strcpy( temp,pers->basename );
433 /* build shader name */
434 _pico_strlwr( temp ); /* gaynux update -sea */
435 sprintf( mapName,"models/mapobjects/%s/%s",pers->basename,temp );
437 /* set shader name */
438 /* PicoSetShaderName( shader,mapName ); */ /* ydnar: this will screw up the named shader */
440 /* set surface's shader index */
441 PicoSetSurfaceShader( pers->surface, shader );
446 /* we didn't set a shader name; throw out warning */
447 if ( !setShaderName ) {
448 _pico_printf( PICO_WARNING,"3DS mesh is missing shader name" );
450 /* we don't process the list of shared vertices here; there is a */
451 /* short int that gives the number of faces of the mesh concerned */
452 /* by this shader, then there is the list itself of these faces. */
453 /* 0000 means the first face of the (4120) face list */
455 /* get number of shared verts */
456 numSharedVerts = GetWord( pers );
459 printf( "GetMeshShader: uses shader '%s' (nsv %d)\n",shaderName,numSharedVerts );
461 /* skip list of shared verts */
462 for ( i = 0; i < numSharedVerts; i++ )
466 /* success (no errors occured) */
470 static int GetDiffuseColor( T3dsLoaderPers *pers ){
471 /* todo: support all 3ds specific color formats; */
472 /* that means: rgb,tru,trug,rgbg */
474 /* get rgb color (range 0..255; 3 bytes) */
477 color[0] = GetByte( pers );
478 color[1] = GetByte( pers );
479 color[2] = GetByte( pers );
482 /* store this as the current shader's diffuse color */
483 if ( pers->shader ) {
484 PicoSetShaderDiffuseColor( pers->shader,color );
487 printf( "GetDiffuseColor: %d %d %d\n",color[0],color[1],color[2] );
489 /* success (no errors occured) */
493 static int DoNextEditorDataChunk( T3dsLoaderPers *pers, long endofs ){
496 #ifdef DEBUG_PM_3DS_EX
497 printf( "DoNextEditorDataChunk: endofs %d\n",endofs );
499 while ( pers->cofs < endofs )
501 long nextofs = pers->cofs;
502 if ( ( chunk = GetChunk( pers ) ) == NULL ) {
508 nextofs += chunk->len;
510 #ifdef DEBUG_PM_3DS_EX
511 printf( "Chunk %04x (%s), len %d pers->cofs %x\n",chunk->id,DebugGetChunkName( chunk->id ),chunk->len,pers->cofs );
514 if ( chunk->id == CHUNK_OBJECT ) {
515 picoSurface_t *surface;
516 char surfaceName[ 0xff ] = { 0 };
518 /* read in surface name */
519 if ( !GetASCIIZ( pers,surfaceName,sizeof( surfaceName ) ) ) {
520 return 0; /* this is bad */
523 /* ignore NULL name surfaces */
526 /* allocate a pico surface */
527 surface = PicoNewSurface( pers->model );
528 if ( surface == NULL ) {
529 pers->surface = NULL;
530 return 0; /* this is bad too */
532 /* assign ptr to current surface */
533 pers->surface = surface;
535 /* 3ds models surfaces are all triangle meshes */
536 PicoSetSurfaceType( pers->surface,PICO_TRIANGLES );
538 /* set surface name */
539 PicoSetSurfaceName( pers->surface,surfaceName );
541 /* continue mess with object's sub chunks */
542 DoNextEditorDataChunk( pers,nextofs );
545 if ( chunk->id == CHUNK_OBJECT_MESH ) {
546 /* continue mess with mesh's sub chunks */
547 if ( !DoNextEditorDataChunk( pers,nextofs ) ) {
552 if ( chunk->id == CHUNK_OBJECT_VERTICES ) {
553 if ( !GetMeshVertices( pers ) ) {
558 if ( chunk->id == CHUNK_OBJECT_FACES ) {
559 if ( !GetMeshFaces( pers ) ) {
564 if ( chunk->id == CHUNK_OBJECT_UV ) {
565 if ( !GetMeshTexCoords( pers ) ) {
570 if ( chunk->id == CHUNK_OBJECT_MATERIAL ) {
571 if ( !GetMeshShader( pers ) ) {
577 if ( chunk->id == CHUNK_MATERIAL ) {
578 /* new shader specific things should be */
579 /* initialized right here */
580 picoShader_t *shader;
582 /* allocate a pico shader */
583 shader = PicoNewShader( pers->model ); /* ydnar */
584 if ( shader == NULL ) {
586 return 0; /* this is bad too */
589 /* assign ptr to current shader */
590 pers->shader = shader;
592 /* continue and process the material's sub chunks */
593 DoNextEditorDataChunk( pers,nextofs );
596 if ( chunk->id == CHUNK_MATNAME ) {
597 /* new material's names should be stored here. note that */
598 /* GetMeshMaterial returns the name of the material that */
599 /* is used by the mesh. new material names are set HERE. */
600 /* but for now we skip the new material's name ... */
601 if ( pers->shader ) {
602 char *name = (char*) ( pers->bufptr + pers->cofs );
603 char *cleanedName = _pico_clone_alloc( name );
604 _pico_first_token( cleanedName );
605 PicoSetShaderName( pers->shader, cleanedName );
607 printf( "NewShader: '%s'\n", cleanedName );
609 _pico_free( cleanedName );
612 if ( chunk->id == CHUNK_MATDIFFUSE ) {
613 /* todo: color for last inserted new material should be */
614 /* stored somewhere by GetDiffuseColor */
615 if ( !GetDiffuseColor( pers ) ) {
619 /* rest of chunk is skipped here */
621 if ( chunk->id == CHUNK_MATMAP ) {
622 /* continue and process the material map sub chunks */
623 DoNextEditorDataChunk( pers,nextofs );
626 if ( chunk->id == CHUNK_MATMAPFILE ) {
627 /* map file name for last inserted new material should */
628 /* be stored here. but for now we skip this too ... */
629 if ( pers->shader ) {
630 char *name = (char *)( pers->bufptr + pers->cofs );
631 PicoSetShaderMapName( pers->shader,name );
633 printf( "NewShaderMapfile: '%s'\n",name );
638 if ( chunk->id == CHUNK_KEYFRAME_DATA ) {
639 /* well umm, this is a bit too much since we don't really */
640 /* need model animation sequences right now. we skip this */
642 printf( "KeyframeData: len %d\n",chunk->len );
645 /* skip unknown chunk */
646 pers->cofs = nextofs;
647 if ( pers->cofs >= pers->maxofs ) {
654 static int DoNextChunk( T3dsLoaderPers *pers, int endofs ){
658 printf( "DoNextChunk: endofs %d\n",endofs );
660 while ( pers->cofs < endofs )
662 long nextofs = pers->cofs;
663 if ( ( chunk = GetChunk( pers ) ) == NULL ) {
669 nextofs += chunk->len;
671 #ifdef DEBUG_PM_3DS_EX
672 printf( "Chunk %04x (%s), len %d pers->cofs %x\n",chunk->id,DebugGetChunkName( chunk->id ),chunk->len,pers->cofs );
675 if ( chunk->id == CHUNK_VERSION ) {
676 /* at this point i get the 3ds file version. since there */
677 /* might be new additions to the 3ds file format in 4.0 */
678 /* it might be a good idea to store the version somewhere */
679 /* for later handling or message displaying */
681 /* get the version */
683 version = GetWord( pers );
686 printf( "FileVersion: %d\n",version );
689 /* throw out a warning for version 4 models */
690 if ( version == 4 ) {
691 _pico_printf( PICO_WARNING,
692 "3DS version is 4. Model might load incorrectly." );
694 /* store the 3ds file version in pico special field 0 */
695 /* PicoSetSurfaceSpecial(pers->surface,0,version); */ /* ydnar: this was causing a crash accessing uninitialized surface */
697 /* rest of chunk is skipped here */
699 /*** editor data ***/
700 if ( chunk->id == CHUNK_EDITOR_DATA ) {
701 if ( !DoNextEditorDataChunk( pers,nextofs ) ) {
706 /* skip unknown chunk */
707 pers->cofs = nextofs;
708 if ( pers->cofs >= pers->maxofs ) {
716 * loads an autodesk 3ds model file.
718 static picoModel_t *_3ds_load( PM_PARAMS_LOAD ){
723 /* create a new pico model */
724 model = PicoNewModel();
725 if ( model == NULL ) {
726 /* user must have some serious ram problems ;) */
729 /* get model's base name (eg. jeep from c:\models\jeep.3ds) */
730 memset( basename,0,sizeof( basename ) );
731 strncpy( basename,_pico_nopath( fileName ),sizeof( basename ) );
732 _pico_setfext( basename,"" );
734 /* initialize persistant vars (formerly static) */
736 pers.bufptr = (picoByte_t *)_pico_alloc( bufSize );
737 memcpy( pers.bufptr, buffer, bufSize );
738 pers.basename = (char *)basename;
739 pers.maxofs = bufSize;
743 PicoSetModelFrameNum( model,frameNum );
744 PicoSetModelName( model,fileName );
745 PicoSetModelFileName( model,fileName );
747 /* skip first chunk in file (magic) */
751 if ( !DoNextChunk( &pers,pers.maxofs ) ) {
752 /* well, bleh i guess */
753 PicoFreeModel( model );
756 /* return allocated pico model */
760 /* pico file format module definition */
761 const picoModule_t picoModule3DS =
763 "0.86-b", /* module version string */
764 "Autodesk 3Dstudio", /* module display name */
765 "seaw0lf", /* author's name */
766 "2002 seaw0lf", /* module copyright */
768 "3ds",NULL,NULL,NULL /* default extensions to use */
770 _3ds_canload, /* validation routine */
771 _3ds_load, /* load routine */
772 NULL, /* save validation routine */
773 NULL /* save routine */