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)
129 int i,max; /* imax? ;) */
130 max = sizeof(debugChunkNames) / sizeof(debugChunkNames[0]);
132 for (i=0; i<max; i++)
134 if (debugChunkNames[i].id == id)
136 /* gaynux update -sea */
137 return _pico_strlwr( debugChunkNames[i].name );
140 return "chunk_unknown";
142 #endif /*DEBUG_PM_3DS*/
144 /* this funky loader needs byte alignment */
145 #pragma pack(push, 1)
147 typedef struct S3dsIndices
149 unsigned short a,b,c;
150 unsigned short visible;
154 typedef struct S3dsChunk
161 /* restore previous data alignment */
165 * validates an autodesk 3ds model file.
167 static int _3ds_canload( PM_PARAMS_CANLOAD )
169 const T3dsChunk *chunk;
172 if (bufSize < (int) sizeof(T3dsChunk))
173 return PICO_PMV_ERROR_SIZE;
175 /* get pointer to 3ds header chunk */
176 chunk = (const T3dsChunk *)buffer;
178 /* check data length */
179 if (bufSize < (int) _pico_little_long(chunk->len))
180 return PICO_PMV_ERROR_SIZE;
182 /* check 3ds magic */
183 if (_pico_little_short(chunk->id) != CHUNK_MAIN)
184 return PICO_PMV_ERROR_IDENT;
186 /* file seems to be a valid 3ds */
190 static T3dsChunk *GetChunk (T3dsLoaderPers *pers)
195 if (pers->cofs > pers->maxofs) return 0;
198 /* printf("GetChunk: pers->cofs %x\n",pers->cofs); */
200 /* fill in pointer to chunk */
201 chunk = (T3dsChunk *)&pers->bufptr[ pers->cofs ];
202 if (!chunk) return NULL;
204 chunk->id = _pico_little_short(chunk->id );
205 chunk->len = _pico_little_long (chunk->len);
207 /* advance in buffer */
208 pers->cofs += sizeof(T3dsChunk);
214 static int GetASCIIZ (T3dsLoaderPers *pers, char *dest, int max)
221 ch = pers->bufptr[ pers->cofs++ ];
222 if (ch == '\0') break;
223 if (pers->cofs >= pers->maxofs)
229 if (pos >= max) break;
235 static picoByte_t GetByte (T3dsLoaderPers *pers)
240 if (pers->cofs > pers->maxofs) return 0;
242 /* get and return value */
243 value = (picoByte_t *)(pers->bufptr + pers->cofs);
248 static int GetWord (T3dsLoaderPers *pers)
250 unsigned short *value;
253 if (pers->cofs > pers->maxofs) return 0;
255 /* get and return value */
256 value = (unsigned short *)(pers->bufptr + pers->cofs);
258 return _pico_little_short(*value);
261 static float GetFloat (T3dsLoaderPers *pers)
266 if (pers->cofs > pers->maxofs) return 0;
268 /* get and return value */
269 value = (float *)(pers->bufptr + pers->cofs);
271 return _pico_little_float(*value);
274 static int GetMeshVertices (T3dsLoaderPers *pers)
279 /* get number of verts for this surface */
280 numVerts = GetWord(pers);
283 printf("GetMeshVertices: numverts %d\n",numVerts);
285 /* read in vertices for current surface */
286 for (i=0; i<numVerts; i++)
289 v[0] = GetFloat( pers );
290 v[1] = GetFloat( pers ); /* ydnar: unflipped */
291 v[2] = GetFloat( pers ); /* ydnar: unflipped and negated */
293 /* add current vertex */
294 PicoSetSurfaceXYZ( pers->surface,i,v );
295 PicoSetSurfaceColor( pers->surface,0,i,white ); /* ydnar */
297 #ifdef DEBUG_PM_3DS_EX
298 printf("Vertex: x: %f y: %f z: %f\n",v[0],v[1],v[2]);
301 /* success (no errors occured) */
305 static int GetMeshFaces (T3dsLoaderPers *pers)
310 /* get number of faces for this surface */
311 numFaces = GetWord(pers);
314 printf("GetMeshFaces: numfaces %d\n",numFaces);
316 /* read in vertex indices for current surface */
317 for (i=0; i<numFaces; i++)
319 /* remember, we only need 3 of 4 values read in for each */
320 /* face. the 4th value is a vis flag for 3dsmax which is */
321 /* being ignored by us here */
323 face.a = GetWord(pers);
324 face.c = GetWord(pers); /* ydnar: flipped order */
325 face.b = GetWord(pers); /* ydnar: flipped order */
326 face.visible = GetWord(pers);
329 PicoSetSurfaceIndex( pers->surface, (i * 3 + 0), (picoIndex_t)face.a );
330 PicoSetSurfaceIndex( pers->surface, (i * 3 + 1), (picoIndex_t)face.b );
331 PicoSetSurfaceIndex( pers->surface, (i * 3 + 2), (picoIndex_t)face.c );
333 #ifdef DEBUG_PM_3DS_EX
334 printf("Face: a: %d b: %d c: %d (%d)\n",face.a,face.b,face.c,face.visible);
337 /* success (no errors occured) */
341 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)
364 PicoSetSurfaceST( pers->surface,0,i,uv );
366 #ifdef DEBUG_PM_3DS_EX
367 printf("u: %f v: %f\n",uv[0],uv[1]);
370 /* success (no errors occured) */
374 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)))
391 /* ydnar: trim to first whitespace */
392 _pico_first_token( shaderName );
394 /* now that we have the shader name we need to go through all of */
395 /* the shaders and check the name against each shader. when we */
396 /* find a shader in our shader list that matches this name we */
397 /* just read in, then we assign the shader's id of the object to */
400 /* get shader id for shader name */
401 shader = PicoFindShader( pers->model, shaderName, 1 );
403 /* we've found a matching shader */
404 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)
419 /* copy map name to local buffer */
420 strcpy( mapName,mapNamePtr );
422 /* extract file name */
423 name = _pico_nopath( mapName );
424 strncpy( temp, name, sizeof(temp) );
426 /* remove file extension */
427 /* name = _pico_setfext( name,"" ); */
429 /* assign default name if no name available */
430 if (strlen(temp) < 1)
431 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 */
449 _pico_printf( PICO_WARNING,"3DS mesh is missing shader name");
451 /* we don't process the list of shared vertices here; there is a */
452 /* short int that gives the number of faces of the mesh concerned */
453 /* by this shader, then there is the list itself of these faces. */
454 /* 0000 means the first face of the (4120) face list */
456 /* get number of shared verts */
457 numSharedVerts = GetWord(pers);
460 printf("GetMeshShader: uses shader '%s' (nsv %d)\n",shaderName,numSharedVerts);
462 /* skip list of shared verts */
463 for (i=0; i<numSharedVerts; i++)
467 /* success (no errors occured) */
471 static int GetDiffuseColor (T3dsLoaderPers *pers)
473 /* todo: support all 3ds specific color formats; */
474 /* that means: rgb,tru,trug,rgbg */
476 /* get rgb color (range 0..255; 3 bytes) */
479 color[0] = GetByte(pers);
480 color[1] = GetByte(pers);
481 color[2] = GetByte(pers);
484 /* store this as the current shader's diffuse color */
487 PicoSetShaderDiffuseColor( pers->shader,color );
490 printf("GetDiffuseColor: %d %d %d\n",color[0],color[1],color[2]);
492 /* success (no errors occured) */
496 static int DoNextEditorDataChunk (T3dsLoaderPers *pers, long endofs)
500 #ifdef DEBUG_PM_3DS_EX
501 printf("DoNextEditorDataChunk: endofs %d\n",endofs);
503 while (pers->cofs < endofs)
505 long nextofs = pers->cofs;
506 if ((chunk = GetChunk(pers)) == NULL) return 0;
507 if (!chunk->len) return 0;
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)
516 picoSurface_t *surface;
517 char surfaceName[ 0xff ] = { 0 };
519 /* read in surface name */
520 if( !GetASCIIZ(pers,surfaceName,sizeof(surfaceName)) )
521 return 0; /* this is bad */
524 /* ignore NULL name surfaces */
527 /* allocate a pico surface */
528 surface = PicoNewSurface( pers->model );
529 if( surface == NULL )
531 pers->surface = NULL;
532 return 0; /* this is bad too */
534 /* assign ptr to current surface */
535 pers->surface = surface;
537 /* 3ds models surfaces are all triangle meshes */
538 PicoSetSurfaceType( pers->surface,PICO_TRIANGLES );
540 /* set surface name */
541 PicoSetSurfaceName( pers->surface,surfaceName );
543 /* continue mess with object's sub chunks */
544 DoNextEditorDataChunk(pers,nextofs);
547 if (chunk->id == CHUNK_OBJECT_MESH)
549 /* continue mess with mesh's sub chunks */
550 if (!DoNextEditorDataChunk(pers,nextofs)) return 0;
553 if (chunk->id == CHUNK_OBJECT_VERTICES)
555 if (!GetMeshVertices(pers)) return 0;
558 if (chunk->id == CHUNK_OBJECT_FACES)
560 if (!GetMeshFaces(pers)) return 0;
563 if (chunk->id == CHUNK_OBJECT_UV)
565 if (!GetMeshTexCoords(pers)) return 0;
568 if (chunk->id == CHUNK_OBJECT_MATERIAL)
570 if (!GetMeshShader(pers)) return 0;
574 if (chunk->id == CHUNK_MATERIAL)
576 /* new shader specific things should be */
577 /* initialized right here */
578 picoShader_t *shader;
580 /* allocate a pico shader */
581 shader = PicoNewShader( pers->model ); /* ydnar */
585 return 0; /* this is bad too */
588 /* assign ptr to current shader */
589 pers->shader = shader;
591 /* continue and process the material's sub chunks */
592 DoNextEditorDataChunk(pers,nextofs);
595 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 ... */
603 char *name = (char*) (pers->bufptr + pers->cofs);
604 char *cleanedName = _pico_clone_alloc( name );
605 _pico_first_token( cleanedName );
606 PicoSetShaderName( pers->shader, cleanedName );
608 printf( "NewShader: '%s'\n", cleanedName );
610 _pico_free( cleanedName );
613 if (chunk->id == CHUNK_MATDIFFUSE)
615 /* todo: color for last inserted new material should be */
616 /* stored somewhere by GetDiffuseColor */
617 if (!GetDiffuseColor(pers)) return 0;
619 /* rest of chunk is skipped here */
621 if (chunk->id == CHUNK_MATMAP)
623 /* continue and process the material map sub chunks */
624 DoNextEditorDataChunk(pers,nextofs);
627 if (chunk->id == CHUNK_MATMAPFILE)
629 /* map file name for last inserted new material should */
630 /* be stored here. but for now we skip this too ... */
633 char *name = (char *)(pers->bufptr + pers->cofs);
634 PicoSetShaderMapName( pers->shader,name );
636 printf("NewShaderMapfile: '%s'\n",name);
641 if (chunk->id == CHUNK_KEYFRAME_DATA)
643 /* well umm, this is a bit too much since we don't really */
644 /* need model animation sequences right now. we skip this */
646 printf("KeyframeData: len %d\n",chunk->len);
649 /* skip unknown chunk */
650 pers->cofs = nextofs;
651 if (pers->cofs >= pers->maxofs) break;
656 static int DoNextChunk (T3dsLoaderPers *pers, int endofs)
661 printf("DoNextChunk: endofs %d\n",endofs);
663 while (pers->cofs < endofs)
665 long nextofs = pers->cofs;
666 if ((chunk = GetChunk(pers)) == NULL) return 0;
667 if (!chunk->len) return 0;
668 nextofs += chunk->len;
670 #ifdef DEBUG_PM_3DS_EX
671 printf("Chunk %04x (%s), len %d pers->cofs %x\n",chunk->id,DebugGetChunkName(chunk->id),chunk->len,pers->cofs);
674 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 */
692 _pico_printf( PICO_WARNING,
693 "3DS version is 4. Model might load incorrectly.");
695 /* store the 3ds file version in pico special field 0 */
696 /* PicoSetSurfaceSpecial(pers->surface,0,version); */ /* ydnar: this was causing a crash accessing uninitialized surface */
698 /* rest of chunk is skipped here */
700 /*** editor data ***/
701 if (chunk->id == CHUNK_EDITOR_DATA)
703 if (!DoNextEditorDataChunk(pers,nextofs)) return 0;
706 /* skip unknown chunk */
707 pers->cofs = nextofs;
708 if (pers->cofs >= pers->maxofs) break;
714 * loads an autodesk 3ds model file.
716 static picoModel_t *_3ds_load( PM_PARAMS_LOAD )
722 /* create a new pico model */
723 model = PicoNewModel();
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))
753 /* well, bleh i guess */
754 PicoFreeModel(model);
757 /* return allocated pico model */
761 /* pico file format module definition */
762 const picoModule_t picoModule3DS =
764 "0.86-b", /* module version string */
765 "Autodesk 3Dstudio", /* module display name */
766 "seaw0lf", /* author's name */
767 "2002 seaw0lf", /* module copyright */
769 "3ds",NULL,NULL,NULL /* default extensions to use */
771 _3ds_canload, /* validation routine */
772 _3ds_load, /* load routine */
773 NULL, /* save validation routine */
774 NULL /* save routine */