1 // 3dsread.cpp
2 //
3 // Copyright (C) 2000, Chris Laurel <claurel@shatters.net>
4 //
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License
7 // as published by the Free Software Foundation; either version 2
8 // of the License, or (at your option) any later version.
9 
10 #include <iomanip>
11 
12 #include "3dschunk.h"
13 #include "3dsmodel.h"
14 #include "3dsread.h"
15 #include <celutil/bytes.h>
16 #include <celutil/debug.h>
17 
18 using namespace std;
19 
20 typedef bool (*ProcessChunkFunc)(ifstream& in,
21                                  unsigned short chunkType,
22                                  int contentSize,
23                                  void*);
24 
25 static int read3DSChunk(ifstream& in,
26                         ProcessChunkFunc chunkFunc,
27                         void* obj);
28 
29 // For pretty printing debug info
30 static int logIndent = 0;
31 
32 
readInt(ifstream & in)33 static int32 readInt(ifstream& in)
34 {
35     int32 ret;
36     in.read((char *) &ret, sizeof(int32));
37     LE_TO_CPU_INT32(ret, ret);
38     return ret;
39 }
40 
readShort(ifstream & in)41 static int16 readShort(ifstream& in)
42 {
43     int16 ret;
44     in.read((char *) &ret, sizeof(int16));
45     LE_TO_CPU_INT16(ret, ret);
46     return ret;
47 }
48 
readUshort(ifstream & in)49 static uint16 readUshort(ifstream& in)
50 {
51     uint16 ret;
52     in.read((char *) &ret, sizeof(uint16));
53     LE_TO_CPU_INT16(ret, ret);
54     return ret;
55 }
56 
readFloat(ifstream & in)57 static float readFloat(ifstream& in)
58 {
59     float f;
60     in.read((char*) &f, sizeof(float));
61     LE_TO_CPU_FLOAT(f, f);
62     return f;
63 }
64 
65 
readChar(ifstream & in)66 static char readChar(ifstream& in)
67 {
68     char c;
69     in.read(&c, 1);
70     return c;
71 }
72 
73 
74 /* Not currently used
75 static int readString(ifstream& in, char* s, int maxLength)
76 {
77     int count;
78     for (count = 0; count < maxLength; count++)
79     {
80         in.read(s + count, 1);
81         if (s[count] == '\0')
82             break;
83     }
84 
85     return count;
86 }*/
87 
readString(ifstream & in)88 static string readString(ifstream& in)
89 {
90     char s[1024];
91     int maxLength = sizeof(s);
92 
93     for (int count = 0; count < maxLength; count++)
94     {
95         in.read(s + count, 1);
96         if (s[count] == '\0')
97             break;
98     }
99 
100     return string(s);
101 }
102 
103 
skipBytes(ifstream & in,int count)104 static void skipBytes(ifstream& in, int count)
105 {
106     char c;
107     while (count-- > 0)
108         in.get(c);
109 }
110 
111 
indent()112 void indent()
113 {
114     for (int i = 0; i < logIndent; i++)
115         cout << "  ";
116 }
117 
logChunk(uint16 chunkType)118 void logChunk(uint16 chunkType/*, int chunkSize*/)
119 {
120     const char* name = NULL;
121 
122     switch (chunkType)
123     {
124     case M3DCHUNK_NULL:
125         name = "M3DCHUNK_NULL"; break;
126     case M3DCHUNK_VERSION:
127         name = "M3DCHUNK_VERSION"; break;
128     case M3DCHUNK_COLOR_FLOAT:
129         name = "M3DCHUNK_COLOR_FLOAT"; break;
130     case M3DCHUNK_COLOR_24:
131         name = "M3DCHUNK_COLOR_24"; break;
132     case M3DCHUNK_LIN_COLOR_F:
133         name = "M3DCHUNK_LIN_COLOR_F"; break;
134     case M3DCHUNK_INT_PERCENTAGE:
135         name = "M3DCHUNK_INT_PERCENTAGE"; break;
136     case M3DCHUNK_FLOAT_PERCENTAGE:
137         name = "M3DCHUNK_FLOAT_PERCENTAGE"; break;
138     case M3DCHUNK_MASTER_SCALE:
139         name = "M3DCHUNK_MASTER_SCALE"; break;
140     case M3DCHUNK_BACKGROUND_COLOR:
141         name = "M3DCHUNK_BACKGROUND_COLOR"; break;
142     case M3DCHUNK_MESHDATA:
143         name = "M3DCHUNK_MESHDATA"; break;
144     case M3DCHUNK_MESH_VERSION:
145         name = "M3DCHUNK_MESHVERSION"; break;
146     case M3DCHUNK_NAMED_OBJECT:
147         name = "M3DCHUNK_NAMED_OBJECT"; break;
148     case M3DCHUNK_TRIANGLE_MESH:
149         name = "M3DCHUNK_TRIANGLE_MESH"; break;
150     case M3DCHUNK_POINT_ARRAY:
151         name = "M3DCHUNK_POINT_ARRAY"; break;
152     case M3DCHUNK_POINT_FLAG_ARRAY:
153         name = "M3DCHUNK_POINT_FLAG_ARRAY"; break;
154     case M3DCHUNK_FACE_ARRAY:
155         name = "M3DCHUNK_FACE_ARRAY"; break;
156     case M3DCHUNK_MESH_MATERIAL_GROUP:
157         name = "M3DCHUNK_MESH_MATERIAL_GROUP"; break;
158     case M3DCHUNK_MESH_TEXTURE_COORDS:
159         name = "M3DCHUNK_MESH_TEXTURE_COORDS"; break;
160     case M3DCHUNK_MESH_SMOOTH_GROUP:
161         name = "M3DCHUNK_MESH_SMOOTH_GROUP"; break;
162     case M3DCHUNK_MESH_MATRIX:
163         name = "M3DCHUNK_MESH_MATRIX"; break;
164     case M3DCHUNK_MAGIC:
165         name = "M3DCHUNK_MAGIC"; break;
166     case M3DCHUNK_MATERIAL_NAME:
167         name = "M3DCHUNK_MATERIAL_NAME"; break;
168     case M3DCHUNK_MATERIAL_AMBIENT:
169         name = "M3DCHUNK_MATERIAL_AMBIENT"; break;
170     case M3DCHUNK_MATERIAL_DIFFUSE:
171         name = "M3DCHUNK_MATERIAL_DIFFUSE"; break;
172     case M3DCHUNK_MATERIAL_SPECULAR:
173         name = "M3DCHUNK_MATERIAL_SPECULAR"; break;
174     case M3DCHUNK_MATERIAL_SHININESS:
175         name = "M3DCHUNK_MATERIAL_SHININESS"; break;
176     case M3DCHUNK_MATERIAL_SHIN2PCT:
177         name = "M3DCHUNK_MATERIAL_SHIN2PCT"; break;
178     case M3DCHUNK_MATERIAL_TRANSPARENCY:
179         name = "M3DCHUNK_MATERIAL_TRANSPARENCY"; break;
180     case M3DCHUNK_MATERIAL_XPFALL:
181         name = "M3DCHUNK_MATERIAL_XPFALL"; break;
182     case M3DCHUNK_MATERIAL_REFBLUR:
183         name = "M3DCHUNK_MATERIAL_REFBLUR"; break;
184     case M3DCHUNK_MATERIAL_TEXMAP:
185         name = "M3DCHUNK_MATERIAL_TEXMAP"; break;
186     case M3DCHUNK_MATERIAL_MAPNAME:
187         name = "M3DCHUNK_MATERIAL_MAPNAME"; break;
188     case M3DCHUNK_MATERIAL_ENTRY:
189         name = "M3DCHUNK_MATERIAL_ENTRY"; break;
190     case M3DCHUNK_KFDATA:
191         name = "M3DCHUNK_KFDATA";
192     default:
193         break;
194     }
195 #if 0
196     indent();
197 
198     if (name == NULL)
199     {
200         cout << "Chunk ID " << setw(4) << hex << setfill('0') << chunkType;
201         cout << setw(0) << dec << ", size = " << chunkSize << '\n';
202     }
203     else
204     {
205         cout << name << ", size = " << chunkSize << '\n';
206     }
207 
208     cout.flush();
209 #endif
210 }
211 
212 
read3DSChunk(ifstream & in,ProcessChunkFunc chunkFunc,void * obj)213 int read3DSChunk(ifstream& in,
214                  ProcessChunkFunc chunkFunc,
215                  void* obj)
216 {
217     unsigned short chunkType = readUshort(in);
218     int32 chunkSize = readInt(in);
219     int contentSize = chunkSize - 6;
220 
221     //logChunk(chunkType/*, chunkSize*/);
222     bool chunkWasRead = chunkFunc(in, chunkType, contentSize, obj);
223 
224     if (!chunkWasRead)
225     {
226         skipBytes(in, contentSize);
227     }
228 
229     return chunkSize;
230 }
231 
232 
read3DSChunks(ifstream & in,int nBytes,ProcessChunkFunc chunkFunc,void * obj)233 int read3DSChunks(ifstream& in,
234                   int nBytes,
235                   ProcessChunkFunc chunkFunc,
236                   void* obj)
237 {
238     int bytesRead = 0;
239 
240     logIndent++;
241     while (bytesRead < nBytes)
242         bytesRead += read3DSChunk(in, chunkFunc, obj);
243     logIndent--;
244 
245     if (bytesRead != nBytes)
246         cout << "Expected " << nBytes << " bytes but read " << bytesRead << '\n';
247     return bytesRead;
248 }
249 
250 
readColor(ifstream & in)251 M3DColor readColor(ifstream& in/*, int nBytes*/)
252 {
253     unsigned char r = (unsigned char) readChar(in);
254     unsigned char g = (unsigned char) readChar(in);
255     unsigned char b = (unsigned char) readChar(in);
256 
257     return M3DColor((float) r / 255.0f,
258                     (float) g / 255.0f,
259                     (float) b / 255.0f);
260 }
261 
262 
readFloatColor(ifstream & in)263 M3DColor readFloatColor(ifstream& in/*, int nBytes*/)
264 {
265     float r = readFloat(in);
266     float g = readFloat(in);
267     float b = readFloat(in);
268 
269     return M3DColor((float) r / 255.0f,
270                     (float) g / 255.0f,
271                     (float) b / 255.0f);
272 }
273 
274 
readMeshMatrix(ifstream & in)275 Mat4f readMeshMatrix(ifstream& in/*, int nBytes*/)
276 {
277     float m00 = readFloat(in);
278     float m01 = readFloat(in);
279     float m02 = readFloat(in);
280     float m10 = readFloat(in);
281     float m11 = readFloat(in);
282     float m12 = readFloat(in);
283     float m20 = readFloat(in);
284     float m21 = readFloat(in);
285     float m22 = readFloat(in);
286     float m30 = readFloat(in);
287     float m31 = readFloat(in);
288     float m32 = readFloat(in);
289 
290 #if 0
291     cout << m00 << "   " << m01 << "   " << m02 << '\n';
292     cout << m10 << "   " << m11 << "   " << m12 << '\n';
293     cout << m20 << "   " << m21 << "   " << m22 << '\n';
294     cout << m30 << "   " << m31 << "   " << m32 << '\n';
295 #endif
296 
297     return Mat4f(Vec4f(m00, m01, m02, 0),
298                  Vec4f(m10, m11, m12, 0),
299                  Vec4f(m20, m21, m22, 0),
300                  Vec4f(m30, m31, m32, 1));
301 }
302 
303 
stubProcessChunk()304 bool stubProcessChunk(/* ifstream& in,
305                          unsigned short chunkType,
306                          int contentSize,
307                          void* obj */)
308 {
309     return false;
310 }
311 
312 
readPointArray(ifstream & in,M3DTriangleMesh * triMesh)313 void readPointArray(ifstream& in, M3DTriangleMesh* triMesh)
314 {
315     uint16 nPoints = readUshort(in);
316 
317     for (int i = 0; i < (int) nPoints; i++)
318     {
319         float x = readFloat(in);
320         float y = readFloat(in);
321         float z = readFloat(in);
322         triMesh->addVertex(Point3f(x, y, z));
323     }
324 }
325 
326 
readTextureCoordArray(ifstream & in,M3DTriangleMesh * triMesh)327 void readTextureCoordArray(ifstream& in, M3DTriangleMesh* triMesh)
328 {
329     uint16 nPoints = readUshort(in);
330 
331     for (int i = 0; i < (int) nPoints; i++)
332     {
333         float u = readFloat(in);
334         float v = readFloat(in);
335         triMesh->addTexCoord(Point2f(u, -v));
336     }
337 }
338 
339 
processFaceArrayChunk(ifstream & in,unsigned short chunkType,int,void * obj)340 bool processFaceArrayChunk(ifstream& in,
341                            unsigned short chunkType,
342                            int /*contentSize*/,
343                            void* obj)
344 {
345     M3DTriangleMesh* triMesh = (M3DTriangleMesh*) obj;
346 
347     if (chunkType == M3DCHUNK_MESH_MATERIAL_GROUP)
348     {
349         // For now, we just assume that there is only one material group
350         // per triangle mesh, and that the material applies to all faces in
351         // the mesh.
352         string materialName = readString(in);
353         uint16 nFaces = readUshort(in);
354 
355         for (uint16 i = 0; i < nFaces; i++)
356         {
357             readUshort(in);
358         }
359 
360         triMesh->setMaterialName(materialName);
361 
362         return true;
363     }
364     else if (chunkType == M3DCHUNK_MESH_SMOOTH_GROUP)
365     {
366         uint16 nFaces = triMesh->getFaceCount();
367 
368         for (uint16 i = 0; i < nFaces; i++)
369         {
370             uint32 groups = (uint32) readInt(in);
371             //            cout << setw(8) << hex << setfill('0') << groups << dec << setw(0) << '\n';
372             triMesh->addSmoothingGroups(groups);
373         }
374         return true;
375     }
376     else
377     {
378         return false;
379     }
380 }
381 
382 
readFaceArray(ifstream & in,M3DTriangleMesh * triMesh,int contentSize)383 void readFaceArray(ifstream& in, M3DTriangleMesh* triMesh, int contentSize)
384 {
385     uint16 nFaces = readUshort(in);
386 
387     for (int i = 0; i < (int) nFaces; i++)
388     {
389         uint16 v0 = readUshort(in);
390         uint16 v1 = readUshort(in);
391         uint16 v2 = readUshort(in);
392         /*uint16 flags = */ readUshort(in);
393         triMesh->addFace(v0, v1, v2);
394     }
395 
396     int bytesLeft = contentSize - (8 * nFaces + 2);
397     if (bytesLeft > 0)
398     {
399         read3DSChunks(in,
400                       bytesLeft,
401                       processFaceArrayChunk,
402                       (void*) triMesh);
403     }
404 }
405 
406 
processTriMeshChunk(ifstream & in,unsigned short chunkType,int contentSize,void * obj)407 bool processTriMeshChunk(ifstream& in,
408                          unsigned short chunkType,
409                          int contentSize,
410                          void* obj)
411 {
412     M3DTriangleMesh* triMesh = (M3DTriangleMesh*) obj;
413 
414     if (chunkType == M3DCHUNK_POINT_ARRAY)
415     {
416         readPointArray(in, triMesh);
417         return true;
418     }
419     else if (chunkType == M3DCHUNK_MESH_TEXTURE_COORDS)
420     {
421         readTextureCoordArray(in, triMesh);
422         return true;
423     }
424     else if (chunkType == M3DCHUNK_FACE_ARRAY)
425     {
426         readFaceArray(in, triMesh, contentSize);
427         return true;
428     }
429     else if (chunkType == M3DCHUNK_MESH_MATRIX)
430     {
431         triMesh->setMatrix(readMeshMatrix(in/*, contentSize*/));
432         return true;
433     }
434     else
435     {
436         return false;
437     }
438 }
439 
440 
processModelChunk(ifstream & in,unsigned short chunkType,int contentSize,void * obj)441 bool processModelChunk(ifstream& in,
442                        unsigned short chunkType,
443                        int contentSize,
444                        void* obj)
445 {
446     M3DModel* model = (M3DModel*) obj;
447 
448     if (chunkType == M3DCHUNK_TRIANGLE_MESH)
449     {
450         M3DTriangleMesh* triMesh = new M3DTriangleMesh();
451         read3DSChunks(in, contentSize, processTriMeshChunk, (void*) triMesh);
452         model->addTriMesh(triMesh);
453         return true;
454     }
455     else
456     {
457         return false;
458     }
459 }
460 
461 
processColorChunk(ifstream & in,unsigned short chunkType,int,void * obj)462 bool processColorChunk(ifstream& in,
463                        unsigned short chunkType,
464                        int /*contentSize*/,
465                        void* obj)
466 {
467     M3DColor* color = (M3DColor*) obj;
468 
469     if (chunkType == M3DCHUNK_COLOR_24)
470     {
471         *color = readColor(in/*, contentSize*/);
472         return true;
473     }
474     else if (chunkType == (M3DCHUNK_COLOR_FLOAT))
475     {
476         *color = readFloatColor(in/*, contentSize*/);
477         return true;
478     }
479     else
480     {
481         return false;
482     }
483 }
484 
485 
processPercentageChunk(ifstream & in,unsigned short chunkType,int,void * obj)486 static bool processPercentageChunk(ifstream& in,
487                                    unsigned short chunkType,
488                                    int /*contentSize*/,
489                                    void* obj)
490 {
491     float* percent = (float*) obj;
492 
493     if (chunkType == M3DCHUNK_INT_PERCENTAGE)
494     {
495         *percent = readShort(in);
496         return true;
497     }
498     else if (chunkType == M3DCHUNK_FLOAT_PERCENTAGE)
499     {
500         *percent = readFloat(in);
501         return true;
502     }
503     else
504     {
505         return false;
506     }
507 }
508 
509 
processTexmapChunk(ifstream & in,unsigned short chunkType,int,void * obj)510 static bool processTexmapChunk(ifstream& in,
511                                unsigned short chunkType,
512                                int /*contentSize*/,
513                                void* obj)
514 {
515     M3DMaterial* material = (M3DMaterial*) obj;
516 
517     if (chunkType == M3DCHUNK_MATERIAL_MAPNAME)
518     {
519         string name = readString(in);
520         material->setTextureMap(name);
521         return true;
522     }
523     else
524     {
525         return false;
526     }
527 }
528 
529 
processMaterialChunk(ifstream & in,unsigned short chunkType,int contentSize,void * obj)530 bool processMaterialChunk(ifstream& in,
531                           unsigned short chunkType,
532                           int contentSize,
533                           void* obj)
534 {
535     M3DMaterial* material = (M3DMaterial*) obj;
536 
537     if (chunkType == M3DCHUNK_MATERIAL_NAME)
538     {
539         string name = readString(in);
540         material->setName(name);
541         return true;
542     }
543     else if (chunkType == M3DCHUNK_MATERIAL_AMBIENT)
544     {
545         M3DColor ambient;
546         read3DSChunks(in, contentSize, processColorChunk, (void*) &ambient);
547         material->setAmbientColor(ambient);
548         return true;
549     }
550     else if (chunkType == M3DCHUNK_MATERIAL_DIFFUSE)
551     {
552         M3DColor diffuse;
553         read3DSChunks(in, contentSize, processColorChunk, (void*) &diffuse);
554         material->setDiffuseColor(diffuse);
555         return true;
556     }
557     else if (chunkType == M3DCHUNK_MATERIAL_SPECULAR)
558     {
559         M3DColor specular;
560         read3DSChunks(in, contentSize, processColorChunk, (void*) &specular);
561         material->setSpecularColor(specular);
562         return true;
563     }
564     else if (chunkType == M3DCHUNK_MATERIAL_SHININESS)
565     {
566         float shininess;
567         read3DSChunks(in, contentSize, processPercentageChunk,
568                       (void*) &shininess);
569         material->setShininess(shininess);
570         return true;
571     }
572     else if (chunkType == M3DCHUNK_MATERIAL_TRANSPARENCY)
573     {
574         float transparency;
575         read3DSChunks(in, contentSize, processPercentageChunk,
576                       (void*) &transparency);
577         material->setOpacity(1.0f - transparency / 100.0f);
578         return true;
579     }
580 
581     else if (chunkType == M3DCHUNK_MATERIAL_TEXMAP)
582     {
583         read3DSChunks(in, contentSize, processTexmapChunk, (void*) material);
584         return true;
585     }
586     else
587     {
588         return false;
589     }
590 }
591 
592 
processSceneChunk(ifstream & in,unsigned short chunkType,int contentSize,void * obj)593 bool processSceneChunk(ifstream& in,
594                        unsigned short chunkType,
595                        int contentSize,
596                        void* obj)
597 {
598     M3DScene* scene = (M3DScene*) obj;
599 
600     if (chunkType == M3DCHUNK_NAMED_OBJECT)
601     {
602         string name = readString(in);
603 
604         M3DModel* model = new M3DModel();
605         model->setName(name);
606         // indent(); cout << "  [" << name << "]\n";
607         read3DSChunks(in,
608                       contentSize - (name.length() + 1),
609                       processModelChunk,
610                       (void*) model);
611         scene->addModel(model);
612 
613         return true;
614     }
615     else if (chunkType == M3DCHUNK_MATERIAL_ENTRY)
616     {
617         M3DMaterial* material = new M3DMaterial();
618         read3DSChunks(in,
619                       contentSize,
620                       processMaterialChunk,
621                       (void*) material);
622         scene->addMaterial(material);
623 
624         return true;
625     }
626     else if (chunkType == M3DCHUNK_BACKGROUND_COLOR)
627     {
628         M3DColor color;
629         read3DSChunks(in, contentSize, processColorChunk, (void*) &color);
630         scene->setBackgroundColor(color);
631         return true;
632     }
633     else
634     {
635         return false;
636     }
637 }
638 
639 
processTopLevelChunk(ifstream & in,unsigned short chunkType,int contentSize,void * obj)640 bool processTopLevelChunk(ifstream& in,
641                           unsigned short chunkType,
642                           int contentSize,
643                           void* obj)
644 {
645     M3DScene* scene = (M3DScene*) obj;
646 
647     if (chunkType == M3DCHUNK_MESHDATA)
648     {
649         read3DSChunks(in, contentSize, processSceneChunk, (void*) scene);
650         return true;
651     }
652     else
653     {
654         return false;
655     }
656 }
657 
658 
Read3DSFile(ifstream & in)659 M3DScene* Read3DSFile(ifstream& in)
660 {
661     unsigned short chunkType = readUshort(in);
662     if (chunkType != M3DCHUNK_MAGIC)
663     {
664         DPRINTF(0, "Read3DSFile: Wrong magic number in header\n");
665         return NULL;
666     }
667 
668     int32 chunkSize = readInt(in);
669     if (in.bad())
670     {
671         DPRINTF(0, "Read3DSFile: Error reading 3DS file.\n");
672         return NULL;
673     }
674 
675     DPRINTF(1, "3DS file, %d bytes\n", chunkSize);
676 
677     M3DScene* scene = new M3DScene();
678     int contentSize = chunkSize - 6;
679 
680     read3DSChunks(in, contentSize, processTopLevelChunk, (void*) scene);
681 
682     return scene;
683 }
684 
685 
Read3DSFile(const string & filename)686 M3DScene* Read3DSFile(const string& filename)
687 {
688     ifstream in(filename.c_str(), ios::in | ios::binary);
689     if (!in.good())
690     {
691         DPRINTF(0, "Read3DSFile: Error opening %s\n", filename.c_str());
692         return NULL;
693     }
694     else
695     {
696         M3DScene* scene = Read3DSFile(in);
697         in.close();
698         return scene;
699     }
700 }
701 
702 
703 #if 0
704 int main(int argc, char* argv[])
705 {
706     if (argc != 2)
707     {
708         cerr << "Usage: 3dsread <filename>\n";
709         exit(1);
710     }
711 
712     ifstream in(argv[1], ios::in | ios::binary);
713     if (!in.good())
714     {
715         cerr << "Error opening " << argv[1] << '\n';
716         exit(1);
717     }
718     else
719     {
720         read3DSFile(in);
721         in.close();
722     }
723 
724     return 0;
725 }
726 #endif
727