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 )
171 /* to keep the compiler happy */
172 *fileName = *fileName;
175 if (bufSize < sizeof(T3dsChunk))
176 return PICO_PMV_ERROR_SIZE;
178 /* get pointer to 3ds header chunk */
179 chunk = (T3dsChunk *)buffer;
181 /* check data length */
182 if (bufSize < _pico_little_long(chunk->len))
183 return PICO_PMV_ERROR_SIZE;
185 /* check 3ds magic */
186 if (_pico_little_short(chunk->id) != CHUNK_MAIN)
187 return PICO_PMV_ERROR_IDENT;
189 /* file seems to be a valid 3ds */
193 static T3dsChunk *GetChunk (T3dsLoaderPers *pers)
198 if (pers->cofs > pers->maxofs) return 0;
201 /* printf("GetChunk: pers->cofs %x\n",pers->cofs); */
203 /* fill in pointer to chunk */
204 chunk = (T3dsChunk *)&pers->bufptr[ pers->cofs ];
205 if (!chunk) return NULL;
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)
224 ch = pers->bufptr[ pers->cofs++ ];
225 if (ch == '\0') break;
226 if (pers->cofs >= pers->maxofs)
232 if (pos >= max) break;
238 static picoByte_t GetByte (T3dsLoaderPers *pers)
243 if (pers->cofs > pers->maxofs) return 0;
245 /* get and return value */
246 value = (picoByte_t *)(pers->bufptr + pers->cofs);
251 static int GetWord (T3dsLoaderPers *pers)
253 unsigned short *value;
256 if (pers->cofs > pers->maxofs) return 0;
258 /* get and return value */
259 value = (unsigned short *)(pers->bufptr + pers->cofs);
261 return _pico_little_short(*value);
264 static float GetFloat (T3dsLoaderPers *pers)
269 if (pers->cofs > pers->maxofs) return 0;
271 /* get and return value */
272 value = (float *)(pers->bufptr + pers->cofs);
274 return _pico_little_float(*value);
277 static int GetMeshVertices (T3dsLoaderPers *pers)
282 /* get number of verts for this surface */
283 numVerts = GetWord(pers);
286 printf("GetMeshVertices: numverts %d\n",numVerts);
288 /* read in vertices for current surface */
289 for (i=0; i<numVerts; i++)
292 v[0] = GetFloat( pers );
293 v[1] = GetFloat( pers ); /* ydnar: unflipped */
294 v[2] = GetFloat( pers ); /* ydnar: unflipped and negated */
296 /* add current vertex */
297 PicoSetSurfaceXYZ( pers->surface,i,v );
298 PicoSetSurfaceColor( pers->surface,0,i,white ); /* ydnar */
300 #ifdef DEBUG_PM_3DS_EX
301 printf("Vertex: x: %f y: %f z: %f\n",v[0],v[1],v[2]);
304 /* success (no errors occured) */
308 static int GetMeshFaces (T3dsLoaderPers *pers)
313 /* get number of faces for this surface */
314 numFaces = GetWord(pers);
317 printf("GetMeshFaces: numfaces %d\n",numFaces);
319 /* read in vertex indices for current surface */
320 for (i=0; i<numFaces; i++)
322 /* remember, we only need 3 of 4 values read in for each */
323 /* face. the 4th value is a vis flag for 3dsmax which is */
324 /* being ignored by us here */
326 face.a = GetWord(pers);
327 face.c = GetWord(pers); /* ydnar: flipped order */
328 face.b = GetWord(pers); /* ydnar: flipped order */
329 face.visible = GetWord(pers);
332 PicoSetSurfaceIndex( pers->surface, (i * 3 + 0), (picoIndex_t)face.a );
333 PicoSetSurfaceIndex( pers->surface, (i * 3 + 1), (picoIndex_t)face.b );
334 PicoSetSurfaceIndex( pers->surface, (i * 3 + 2), (picoIndex_t)face.c );
336 #ifdef DEBUG_PM_3DS_EX
337 printf("Face: a: %d b: %d c: %d (%d)\n",face.a,face.b,face.c,face.visible);
340 /* success (no errors occured) */
344 static int GetMeshTexCoords (T3dsLoaderPers *pers)
349 /* get number of uv coords for this surface */
350 numTexCoords = GetWord(pers);
353 printf("GetMeshTexCoords: numcoords %d\n",numTexCoords);
355 /* read in uv coords for current surface */
356 for (i=0; i<numTexCoords; i++)
359 uv[0] = GetFloat( pers );
360 uv[1] = -GetFloat( pers ); /* ydnar: we use origin at bottom */
362 /* to make sure we don't mess up memory */
363 if (pers->surface == NULL)
367 PicoSetSurfaceST( pers->surface,0,i,uv );
369 #ifdef DEBUG_PM_3DS_EX
370 printf("u: %f v: %f\n",uv[0],uv[1]);
373 /* success (no errors occured) */
377 static int GetMeshShader (T3dsLoaderPers *pers)
379 char shaderName[255] = { 0 };
380 picoShader_t *shader;
382 int setShaderName = 0;
385 /* the shader is either the color or the texture map of the */
386 /* object. it can also hold other information like the brightness, */
387 /* shine, etc. stuff we don't really care about. we just want the */
388 /* color, or the texture map file name really */
390 /* get in the shader name */
391 if (!GetASCIIZ(pers,shaderName,sizeof(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 PicoSetShaderName( pers->shader,name );
606 printf("NewShader: '%s'\n",name);
610 if (chunk->id == CHUNK_MATDIFFUSE)
612 /* todo: color for last inserted new material should be */
613 /* stored somewhere by GetDiffuseColor */
614 if (!GetDiffuseColor(pers)) return 0;
616 /* rest of chunk is skipped here */
618 if (chunk->id == CHUNK_MATMAP)
620 /* continue and process the material map sub chunks */
621 DoNextEditorDataChunk(pers,nextofs);
624 if (chunk->id == CHUNK_MATMAPFILE)
626 /* map file name for last inserted new material should */
627 /* be stored here. but for now we skip this too ... */
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)
640 /* well umm, this is a bit too much since we don't really */
641 /* need model animation sequences right now. we skip this */
643 printf("KeyframeData: len %d\n",chunk->len);
646 /* skip unknown chunk */
647 pers->cofs = nextofs;
648 if (pers->cofs >= pers->maxofs) break;
653 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) return 0;
664 if (!chunk->len) return 0;
665 nextofs += chunk->len;
667 #ifdef DEBUG_PM_3DS_EX
668 printf("Chunk %04x (%s), len %d pers->cofs %x\n",chunk->id,DebugGetChunkName(chunk->id),chunk->len,pers->cofs);
671 if (chunk->id == CHUNK_VERSION)
673 /* at this point i get the 3ds file version. since there */
674 /* might be new additions to the 3ds file format in 4.0 */
675 /* it might be a good idea to store the version somewhere */
676 /* for later handling or message displaying */
678 /* get the version */
680 version = GetWord(pers);
683 printf("FileVersion: %d\n",version);
686 /* throw out a warning for version 4 models */
689 _pico_printf( PICO_WARNING,
690 "3DS version is 4. Model might load incorrectly.");
692 /* store the 3ds file version in pico special field 0 */
693 /* PicoSetSurfaceSpecial(pers->surface,0,version); */ /* ydnar: this was causing a crash accessing uninitialized surface */
695 /* rest of chunk is skipped here */
697 /*** editor data ***/
698 if (chunk->id == CHUNK_EDITOR_DATA)
700 if (!DoNextEditorDataChunk(pers,nextofs)) return 0;
703 /* skip unknown chunk */
704 pers->cofs = nextofs;
705 if (pers->cofs >= pers->maxofs) break;
711 * loads an autodesk 3ds model file.
713 static picoModel_t *_3ds_load( PM_PARAMS_LOAD )
719 /* create a new pico model */
720 model = PicoNewModel();
723 /* user must have some serious ram problems ;) */
726 /* get model's base name (eg. jeep from c:\models\jeep.3ds) */
727 memset( basename,0,sizeof(basename) );
728 strncpy( basename,_pico_nopath(fileName),sizeof(basename) );
729 _pico_setfext( basename,"" );
731 /* initialize persistant vars (formerly static) */
733 pers.bufptr = (picoByte_t *)buffer;
734 pers.basename = (char *)basename;
735 pers.maxofs = bufSize;
739 PicoSetModelFrameNum( model,frameNum );
740 PicoSetModelName( model,fileName );
741 PicoSetModelFileName( model,fileName );
743 /* skip first chunk in file (magic) */
747 if (!DoNextChunk(&pers,pers.maxofs))
749 /* well, bleh i guess */
750 PicoFreeModel(model);
753 /* return allocated pico model */
757 /* pico file format module definition */
758 const picoModule_t picoModule3DS =
760 "0.86-b", /* module version string */
761 "Autodesk 3Dstudio", /* module display name */
762 "seaw0lf", /* author's name */
763 "2002 seaw0lf", /* module copyright */
765 "3ds",NULL,NULL,NULL /* default extensions to use */
767 _3ds_canload, /* validation routine */
768 _3ds_load, /* load routine */
769 NULL, /* save validation routine */
770 NULL /* save routine */