/* GL-117 Copyright 2001, 2002 Thomas A. Drexl aka heptargon This file is part of GL-117. GL-117 is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. GL-117 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GL-117; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* This file includes a 3ds loader. Thanks to the great NeHe Tutorials! */ #ifndef IS_3DS_H #include #include #include #include #include #include "3ds.h" #include "common.h" #include "gl.h" #include "dirs.h" #include "mathtab.h" FILE *debugstream3ds = stdout; BinaryFile::BinaryFile (char *filename) { char buf [STDSIZE]; in = fopen (filename, "rb"); if (in == NULL) { sprintf (buf, "Cannot open file %s", filename); display (buf, LOG_FATAL); exit (EXIT_LOADFILE); } fseek (in, 0, SEEK_END); size = ftell (in); fseek (in, 0, SEEK_SET); data = new unsigned char [size]; if (data == NULL) error_outofmemory (); memset (data, 0, size); Uint32 z = 0; while (z < size) { int tmp; if (size - z >= 4096) tmp = fread (&data [z], 1, 4096, in); else tmp = fread (&data [z], 1, size - z, in); z += 4096; } fclose (in); filepointer = 0; } BinaryFile::~BinaryFile () { delete data; } int BinaryFile::readFloat (float *f) { #ifdef WORDS_BIGENDIAN ret [0] = data [filepointer + 3]; ret [1] = data [filepointer + 2]; ret [2] = data [filepointer + 1]; ret [3] = data [filepointer]; ret [4] = 0; *f = *((float *) ret); #else *f = *((float *) &data [filepointer]); #endif filepointer += 4; return 4; } int BinaryFile::readFloat (float *f, int n) { int i; for (i = 0; i < n; i ++) { readFloat (&f [i]); } return n * 4; } int BinaryFile::readUInt32 (Uint32 *i) { #ifdef WORDS_BIGENDIAN ret [0] = data [filepointer + 3]; ret [1] = data [filepointer + 2]; ret [2] = data [filepointer + 1]; ret [3] = data [filepointer]; ret [4] = 0; *i = *((Uint32 *) ret); #else *i = *((Uint32 *) &data [filepointer]); #endif filepointer += 4; return 4; } int BinaryFile::readUInt16 (Uint16 *i) { #ifdef WORDS_BIGENDIAN ret [0] = data [filepointer + 1]; ret [1] = data [filepointer + 0]; ret [2] = 0; *i = *((Uint16 *) ret); #else *i = *((Uint16 *) &data [filepointer]); #endif filepointer += 2; return 2; } int BinaryFile::readString (char *ptr, int ptrmax, int n) { if (n <= 0) return 0; if (filepointer + n > size) n = size - filepointer; if (n > ptrmax) exit (200); memcpy (ptr, &data [filepointer], n); filepointer += n; return n; } int BinaryFile::readString (char *ptr, int n) { if (n <= 0) return 0; if (filepointer + n > size) n = size - filepointer; memcpy (ptr, &data [filepointer], n); filepointer += n; return n; } int BinaryFile::readString (char *ptr) { int i = 0; while (data [filepointer] != 0 && filepointer < size) { ptr [i] = data [filepointer]; i ++; filepointer ++; } ptr [i] = 0; filepointer ++; i ++; return i; } int BinaryFile::skip (int n) { if (filepointer + n > size) n = size - filepointer; filepointer += n; return n; } /**************************************************************************** 3DS LOADER ****************************************************************************/ // Contructor -> initialization CLoad3DS::CLoad3DS () { currentChunk = new Chunk; if (currentChunk == NULL) error_outofmemory (); tempChunk = new Chunk; if (tempChunk == NULL) error_outofmemory (); } // Load the whole 3DS file using the private functions below bool CLoad3DS::Import3DS (CModel *model, char *filename) { char message [255] = {0}; file = new BinaryFile (filename); if (file == NULL) error_outofmemory (); // Read the first chuck ReadChunk(currentChunk); // Make sure this is a 3DS file if (currentChunk->ID != PRIMARY) { sprintf (message, "Unable to load PRIMARY chuck from file: %s", filename); display (message, LOG_FATAL); exit (EXIT_LOADFILE); } // Load objects recursively ProcessNextChunk (model, currentChunk); // Merge equal vertices Compile (model); // Calculate vertex normals ComputeNormals (model); // Load textures LoadTextures (model); // Set vertex colors using textures ComputeColors (model); // Set vertices to [(-1,-1,-1);(1,1,1)] Normalize (model); // Clean up CleanUp(); return true; } void CLoad3DS::CleanUp () { delete file; // fclose (filepointer); // Close the current file pointer /* Vorsicht: Speicherleck !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!*/ //delete currentChunk; // Free the current chunk //delete tempChunk; // Free our temporary chunk } void CLoad3DS::ProcessNextChunk (CModel *model, Chunk *previousChunk) { char version [10]; char buf [STDSIZE]; CObject newObject; CMaterial newTexture; currentChunk = new Chunk; if (currentChunk == NULL) error_outofmemory (); while (previousChunk->bytesRead < previousChunk->length) { ReadChunk(currentChunk); if (currentChunk->ID == 0 && currentChunk->length == 0) exit (1000); if (debug3ds) { fprintf (debugstream3ds, "%X: ", currentChunk->ID); fflush (debugstream3ds); } switch (currentChunk->ID) { case VERSION: currentChunk->bytesRead += file->readString (version, 10, currentChunk->length - currentChunk->bytesRead); if (version [0] > 0x03) { sprintf (buf, "This 3DS file is over version 3 so it may load incorrectly"); display (buf, LOG_WARN); } if (debug3ds) { fprintf (debugstream3ds, "Version %d\n", version [0]); fflush (debugstream3ds); } break; case OBJECTINFO: ReadChunk (tempChunk); tempChunk->bytesRead += file->readString (version, 10, tempChunk->length - tempChunk->bytesRead); currentChunk->bytesRead += tempChunk->bytesRead; ProcessNextChunk (model, currentChunk); break; case MATERIAL: model->addMaterial (&newTexture); ProcessNextMaterialChunk (model, currentChunk); break; case OBJECT: model->addObject (&newObject); memset (&newObject, 0, sizeof (CObject)); currentChunk->bytesRead += GetString (model->object [model->numObjects - 1]->name); ProcessNextObjectChunk (model, (model->object [model->numObjects - 1]), currentChunk); break; case MAINSCALE: float mainscale; currentChunk->bytesRead += file->readFloat (&mainscale); model->scale = mainscale; currentChunk->bytesRead += file->skip (currentChunk->length - currentChunk->bytesRead); break; case EDITKEYFRAME: currentChunk->bytesRead += file->skip (currentChunk->length - currentChunk->bytesRead); break; default: currentChunk->bytesRead += file->skip (currentChunk->length - currentChunk->bytesRead); break; } previousChunk->bytesRead += currentChunk->bytesRead; } delete currentChunk; currentChunk = previousChunk; } void CLoad3DS::ProcessNextObjectChunk (CModel *model, CObject *object, Chunk *previousChunk) { currentChunk = new Chunk; if (currentChunk == NULL) error_outofmemory (); while (previousChunk->bytesRead < previousChunk->length) { ReadChunk(currentChunk); if (debug3ds) { fprintf (debugstream3ds, "OBJ %X: ", currentChunk->ID); fflush (debugstream3ds); } switch (currentChunk->ID) { case OBJECT_MESH: ProcessNextObjectChunk (model, object, currentChunk); break; case OBJECT_VERTICES: ReadVertices (object, currentChunk); break; case OBJECT_FACES: // triangles ReadVertexIndices (object, currentChunk); break; case OBJECT_MATERIAL: ReadObjectMaterial (model, object, currentChunk); break; case OBJECT_UV: ReadUVCoordinates (object, currentChunk); break; case OBJECT_MESHMATRIX: ReadMeshMatrix (object, currentChunk); break; default: currentChunk->bytesRead += file->skip (currentChunk->length - currentChunk->bytesRead); break; } previousChunk->bytesRead += currentChunk->bytesRead; } delete currentChunk; currentChunk = previousChunk; } void CLoad3DS::ProcessNextMaterialChunk (CModel *model, Chunk *previousChunk) { currentChunk = new Chunk; if (currentChunk == NULL) error_outofmemory (); while (previousChunk->bytesRead < previousChunk->length) { ReadChunk(currentChunk); if (debug3ds) { fprintf (debugstream3ds, "MAT %X: ", currentChunk->ID); fflush (debugstream3ds); } switch (currentChunk->ID) { case MAT_NAME: currentChunk->bytesRead += file->readString (model->material [model->numMaterials - 1]->name, 255, currentChunk->length - currentChunk->bytesRead); break; case MAT_DIFFUSE: ReadColorChunk (model->material [model->numMaterials - 1], currentChunk); break; case MAT_MAP: ProcessNextMaterialChunk (model, currentChunk); break; case MAT_MAPFILE: currentChunk->bytesRead += file->readString (model->material [model->numMaterials - 1]->filename, 255, currentChunk->length - currentChunk->bytesRead); { char *str = model->material [model->numMaterials - 1]->filename; while (*str) { if (*str >= 'A' && *str <= 'Z') *str = tolower (*str); str++; } } break; case MAT_USCALE: ReadUScale (model->material [model->numMaterials - 1], currentChunk); break; case MAT_VSCALE: ReadVScale (model->material [model->numMaterials - 1], currentChunk); break; case MAT_UOFFSET: ReadUOffset (model->material [model->numMaterials - 1], currentChunk); break; case MAT_VOFFSET: ReadVOffset (model->material [model->numMaterials - 1], currentChunk); break; case MAT_ROTATION: ReadUVRotation (model->material [model->numMaterials - 1], currentChunk); break; default: currentChunk->bytesRead += file->skip (currentChunk->length - currentChunk->bytesRead); break; } previousChunk->bytesRead += currentChunk->bytesRead; } delete currentChunk; currentChunk = previousChunk; } void CLoad3DS::ReadChunk (Chunk *pChunk) { pChunk->bytesRead = file->readUInt16 (&pChunk->ID); pChunk->bytesRead += file->readUInt32 (&pChunk->length); } int CLoad3DS::GetString (char *buffer) { return file->readString (buffer); } void CLoad3DS::ReadColorChunk (CMaterial *material, Chunk *pChunk) { ReadChunk (tempChunk); tempChunk->bytesRead += file->readString ((char *) material->color.c, 4, tempChunk->length - tempChunk->bytesRead); pChunk->bytesRead += tempChunk->bytesRead; } void CLoad3DS::ReadVertexIndices (CObject *object, Chunk *previousChunk) { Uint16 index = 0; previousChunk->bytesRead += file->readUInt16 ((Uint16 *) &object->numTriangles); object->triangle = new CTriangle [object->numTriangles]; if (object->triangle == NULL) error_outofmemory (); memset (object->triangle, 0, sizeof (CTriangle) * object->numTriangles); for (int i = 0; i < object->numTriangles; i ++) { for (int j = 0; j < 4; j++) { previousChunk->bytesRead += file->readUInt16 (&index); if (j < 3) { object->triangle [i].v [j] = &object->vertex [index]; } } } } void CLoad3DS::ReadMeshMatrix (CObject *object, Chunk *previousChunk) { float matrix [12]; previousChunk->bytesRead += file->readFloat ((float *) matrix, (previousChunk->length - previousChunk->bytesRead) / 4); // Where to put theses coords??? } void CLoad3DS::ReadUScale (CMaterial *material, Chunk *previousChunk) { previousChunk->bytesRead += file->readFloat (&material->uscale, 1); previousChunk->bytesRead += file->skip (currentChunk->length - currentChunk->bytesRead); } void CLoad3DS::ReadVScale (CMaterial *material, Chunk *previousChunk) { previousChunk->bytesRead += file->readFloat (&material->vscale, 1); previousChunk->bytesRead += file->skip (currentChunk->length - currentChunk->bytesRead); } void CLoad3DS::ReadUOffset (CMaterial *material, Chunk *previousChunk) { previousChunk->bytesRead += file->readFloat (&material->uoffset, 1); previousChunk->bytesRead += file->skip (currentChunk->length - currentChunk->bytesRead); } void CLoad3DS::ReadVOffset (CMaterial *material, Chunk *previousChunk) { previousChunk->bytesRead += file->readFloat (&material->voffset, 1); previousChunk->bytesRead += file->skip (currentChunk->length - currentChunk->bytesRead); } void CLoad3DS::ReadUVRotation (CMaterial *material, Chunk *previousChunk) { previousChunk->bytesRead += file->readFloat (&material->wrot, 1); previousChunk->bytesRead += file->skip (currentChunk->length - currentChunk->bytesRead); } void CLoad3DS::ReadUVCoordinates (CObject *object, Chunk *previousChunk) { previousChunk->bytesRead += file->readUInt16 ((Uint16 *) &object->numTexVertex); CVector2 *p = new CVector2 [object->numTexVertex]; if (p == NULL) error_outofmemory (); previousChunk->bytesRead += file->readFloat ((float *) p, (previousChunk->length - previousChunk->bytesRead) / 4); for (int i = 0; i < object->numTexVertex; i ++) object->vertex [i].tex.take (&p [i]); delete p; } void CLoad3DS::ReadVertices (CObject *object, Chunk *previousChunk) { int i; previousChunk->bytesRead += file->readUInt16 ((Uint16 *) &object->numVertices); object->vertex = new CVertex [object->numVertices]; if (object->vertex == NULL) error_outofmemory (); memset (object->vertex, 0, sizeof (CVertex) * object->numVertices); CVector3 *p = new CVector3 [object->numVertices]; if (p == NULL) error_outofmemory (); previousChunk->bytesRead += file->readFloat ((float *) p, (previousChunk->length - previousChunk->bytesRead) / 4); for (i = 0; i < object->numVertices; i ++) { object->vertex [i].vector.take (&p [i]); } // Flip the y values with the z values as 3DMAX changed them and negate z for (i = 0; i < object->numVertices; i ++) { float fTempY = object->vertex [i].vector.y; object->vertex [i].vector.y = object->vertex [i].vector.z; object->vertex [i].vector.z = -fTempY; } delete p; } void CLoad3DS::ReadObjectMaterial (CModel *model, CObject *object, Chunk *previousChunk) { char materialName [255] = {0}; previousChunk->bytesRead += GetString (materialName); for (int i = 0; i < model->numMaterials; i ++) { if (strcmp (materialName, model->material [i]->name) == 0) { object->material = model->material [i]; if (strlen (model->material [i]->filename) > 0) if ((model->material [i]->filename [0] >= 'A' && model->material [i]->filename [0] <= 'Z') || (model->material [i]->filename [0] >= 'a' && model->material [i]->filename [0] <= 'z')) { object->hasTexture = true; } break; } else { object->material = NULL; } } currentChunk->bytesRead += file->skip (currentChunk->length - currentChunk->bytesRead); } void CLoad3DS::Compile (CModel *model) { // Merge numerically equal vertices // This is necessary to get a smooth shaded object int i; for (i = 0; i < model->numObjects; i ++) { CObject *co = model->object [i]; for (int i2 = 1; i2 < co->numVertices; i2 ++) { for (int i3 = 0; i3 < i2; i3 ++) { if (co->vertex [i2].vector.isEqual (&co->vertex [i3].vector) && // same coordinates co->vertex [i2].tex.isEqual (&co->vertex [i3].tex)) // same texture coordinates { for (int i4 = 0; i4 < co->numTriangles; i4 ++) { if (co->triangle [i4].v [0] == &co->vertex [i2]) co->triangle [i4].v [0] = &co->vertex [i3]; if (co->triangle [i4].v [1] == &co->vertex [i2]) co->triangle [i4].v [1] = &co->vertex [i3]; if (co->triangle [i4].v [2] == &co->vertex [i2]) co->triangle [i4].v [2] = &co->vertex [i3]; } } } } } // Scale texture coordinated by uscale, vscale for (i = 0; i < model->numObjects; i ++) { CObject *co = model->object [i]; float uscale = co->material->uscale; float vscale = co->material->vscale; float uoffset = co->material->uoffset; float voffset = co->material->voffset; for (int i2 = 0; i2 < co->numVertices; i2 ++) { float ax = co->vertex [i2].tex.x - 0.5; float ay = co->vertex [i2].tex.y - 0.5; float phi = -co->material->wrot; co->vertex [i2].tex.x = ax * COS(phi) - ay * SIN(phi) + 0.5; co->vertex [i2].tex.y = ax * SIN(phi) + ay * COS(phi) + 0.5; co->vertex [i2].tex.x -= uoffset; co->vertex [i2].tex.y += voffset; co->vertex [i2].tex.x = (co->vertex [i2].tex.x - 0.5) * uscale + 0.5; co->vertex [i2].tex.y = (co->vertex [i2].tex.y - 0.5) * vscale + 0.5; } } } void CLoad3DS::ComputeNormals (CModel *model) { int i, i2, i3; if (model->numObjects <= 0) return; CVector3 n; for (i = 0; i < model->numObjects; i ++) { CObject *object = (model->object [i]); for (i2 = 0; i2 < object->numTriangles; i2 ++) { object->triangle [i2].getNormal (&n); for (i3 = 0; i3 < 3; i3 ++) { object->triangle [i2].v [i3]->addNormal (&n); } } for (i2 = 0; i2 < object->numTriangles; i2 ++) { for (i3 = 0; i3 < 3; i3 ++) { object->triangle [i2].v [i3]->normal.norm (); } } } } void CLoad3DS::LoadTextures (CModel *model) { int i, i2; char str [256]; for (i = 0; i < model->numObjects; i ++) { if (model->object [i]->hasTexture) { strcpy (str, dirs->getTextures (model->object [i]->material->filename)); for (i2 = (int) strlen (str) - 1; i2 >= 0; i2 --) { if (i2 > 2 && str [i2] == '.') { str [i2+1] = 't'; str [i2+2] = 'g'; str [i2+3] = 'a'; str [i2+4] = 0; break; } } model->object [i]->material->texture = gl->genTextureTGA (str, 0, -1, 1, false); if (model->object [i]->material->texture == NULL) model->object [i]->hasTexture = false; } } } void CLoad3DS::ComputeColors (CModel *model) { int i, i2; if (model->numObjects <= 0) return; CColor c; for (i = 0; i < model->numObjects; i ++) { CObject *object = (model->object [i]); for (i2 = 0; i2 < object->numVertices; i2 ++) { if (object->hasTexture) { CVertex *v = &object->vertex [i2]; CTexture *tex = object->material->texture; tex->getColor (&c, (int) (v->tex.x * tex->width), (int) (v->tex.y * tex->height)); int val; if (c.c [0] < 200 || c.c [1] < 200) { val = c.c [0]; val = val * 2 / 3; c.c [0] = val; val = c.c [1]; val = val * 2 / 3; c.c [1] = val; val = c.c [2]; val = val * 2 / 3; c.c [2] = val; } v->color.take (&c); } else { if (object->material) { object->vertex [i2].color.c [0] = object->material->color.c [0]; object->vertex [i2].color.c [1] = object->material->color.c [1]; object->vertex [i2].color.c [2] = object->material->color.c [2]; } else { object->vertex [i2].color.c [0] = 200; object->vertex [i2].color.c [1] = 200; object->vertex [i2].color.c [2] = 200; } } } } } void CLoad3DS::Normalize (CModel *model) { int i, i2; float minx = 1E10, miny = 1E10, minz = 1E10; float maxx = -1E10, maxy = -1E10, maxz = -1E10; for (i = 0; i < model->numObjects; i ++) { CObject *object = (model->object [i]); for (i2 = 0; i2 < object->numVertices; i2 ++) { CVertex *v = &object->vertex [i2]; if (v->vector.x > maxx) maxx = v->vector.x; if (v->vector.y > maxy) maxy = v->vector.y; if (v->vector.z > maxz) maxz = v->vector.z; if (v->vector.x < minx) minx = v->vector.x; if (v->vector.y < miny) miny = v->vector.y; if (v->vector.z < minz) minz = v->vector.z; } } float tlx = (maxx + minx) / 2.0; float tly = (maxy + miny) / 2.0; float tlz = (maxz + minz) / 2.0; model->scalex = (maxx - minx) * 0.5; model->scaley = (maxy - miny) * 0.5; model->scalez = (maxz - minz) * 0.5; float sc = model->scalex > model->scaley ? model->scalex : model->scaley; sc = model->scalez > sc ? model->scalez : sc; for (i = 0; i < model->numObjects; i ++) { CObject *object = (model->object [i]); for (i2 = 0; i2 < object->numVertices; i2 ++) { CVertex *v = &object->vertex [i2]; v->vector.x -= tlx; v->vector.x /= sc; v->vector.y -= tly; v->vector.y /= sc; v->vector.z -= tlz; v->vector.z /= sc; } } } #endif