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