/* orxonox - the future of 3D-vertical-scrollers Copyright (C) 2004 orx This program 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, or (at your option) any later version. ### File Specific: main-programmer: Benjamin Grauer co-programmer: ... */ #include "model.h" #include #include #include "array.h" #include "../vector.h" using namespace std; /** \brief Creates a 3D-Model. This only initializes a 3D-Model, but does not cleanup the Faces. */ Model::Model(void) { this->initialize(); } /** \brief Creates a 3D-Model of Primitive-Type type if you want to just display a Cube/Sphere/Cylinder/... without any material. \todo implement Cube/Sphere/Cylinder/... */ Model::Model(PRIMITIVE type) { this->initialize(); switch (type) { default: case CUBE: this->cubeModel(); break; case SPHERE: this->sphereModel(); break; case CYLINDER: this->cylinderModel(); break; } this->importToGL (); this->cleanup(); } /** \brief Creates a 3D-Model. and assigns it a Name. */ Model::Model(char* modelName) { this->initialize(); this->setName(modelName); } /** \brief deletes an Model. Looks if any from model allocated space is still in use, and if so deleted it. */ Model::~Model(void) { PRINTF(3)("Deleting Model "); if (this->name) { PRINT(3)("%s\n", this->name); delete []this->name; } else PRINT(3)("\n"); PRINTF(3)("Deleting display Lists.\n"); Group* walker = this->firstGroup; while (walker != NULL) { glDeleteLists (walker->listNumber, 1); Group* delWalker = walker; walker = walker->next; delete delWalker; } PRINTF(3)("Deleting Materials.\n"); if (this->material) delete this->material; } /** \brief Finalizes an Object. This can be done outside of the Class. */ void Model::finalize(void) { this->importToGL (); this->cleanup(); this->finalized = true; } /** \brief Draws the Models of all Groups. It does this by just calling the Lists that must have been created earlier. */ void Model::draw (void) const { PRINTF(2)("drawing the 3D-Models\n"); Group* walker = this->firstGroup; while (walker != NULL) { PRINTF(3)("Drawing model %s\n", walker->name); glCallList (walker->listNumber); walker = walker->next; } } /** \brief Draws the Model number groupNumber \param groupNumber The number of the group that will be displayed. It does this by just calling the List that must have been created earlier. */ void Model::draw (int groupNumber) const { if (groupNumber >= this->groupCount) { PRINTF(1)("You requested model number %i, but this File only contains of %i Models.\n", groupNumber-1, this->groupCount); return; } PRINTF(2)("drawing the requested 3D-Models if found.\n"); Group* walker = this->firstGroup; int counter = 0; while (walker != NULL) { if (counter == groupNumber) { PRINTF(2)("Drawing model number %i named %s\n", counter, walker->name); glCallList (walker->listNumber); return; } ++counter; walker = walker->next; } PRINTF(1)("Model number %i in %s not Found.\n", groupNumber, this->name); return; } /** \brief Draws the Model with a specific groupName \param groupName The name of the group that will be displayed. It does this by just calling the List that must have been created earlier. */ void Model::draw (char* groupName) const { PRINTF(2)("drawing the requested 3D-Models if found.\n"); Group* walker = this->firstGroup; while (walker != NULL) { if (!strcmp(walker->name, groupName)) { PRINTF(2)("Drawing model %s\n", walker->name); glCallList (walker->listNumber); return; } walker = walker->next; } PRINTF(1)("Model Named %s in %s not Found.\n", groupName, this->name); return; } /** \returns Count of the Models in this File */ int Model::getGroupCount (void) const { return this->groupCount; } /** \brief initializes the Model. This Function initializes all the needed arrays, Lists and clientStates. It also defines default values. */ bool Model::initialize (void) { PRINTF(2)("new 3D-Model is being created\n"); this->name = NULL; this->finalized = false; // setting the start group; this->firstGroup = new Group; this->currentGroup = this->firstGroup; this->groupCount = 0; this->initGroup (this->currentGroup); this->scaleFactor = 1; this->material = new Material(); this->vertices = new Array(); this->vTexture = new Array(); this->normals = new Array(); return true; } void Model::setName(const char* name) { if (this->name) delete this->name; this->name = new char[strlen(name)+1]; strcpy(this->name, name); } /** \brief initializes a new Group model \param group the group that should be initialized. \todo Maybe Group should be a Class, because it does a lot of stuff */ bool Model::initGroup(Group* group) { PRINTF(3)("Adding new Group\n"); group->name = ""; group->faceMode = -1; group->faceCount = 0; group->next = NULL; group->firstFace = new Face; this->initFace (group->firstFace); group->currentFace = group->firstFace; } /** \brief initializes a new Face. (sets default Values) \param face The face to initialize */ bool Model::initFace (Face* face) { face->vertexCount = 0; face->firstElem = NULL; face->materialString = NULL; face->next = NULL; return true; } /** \brief finalizes an Model. This funcion is needed, to delete all the Lists, and arrays that are no more needed because they are already imported into openGL. This will be applied at the end of the importing Process. */ bool Model::cleanup(void) { PRINTF(3)("cleaning up the 3D-Model to save Memory.\n"); if (this->vertices) delete this->vertices; if (this->vTexture) delete this->vTexture; if (this->normals) delete this->normals; this->cleanupGroup(this->firstGroup); return true; } /** \brief Cleans up all groups starting from group. \param group the first Group to clean */ bool Model::cleanupGroup (Group* group) { PRINTF(3)("Cleaning up group\n"); if (group->firstFace != NULL) { cleanupFace (group->firstFace); delete group->firstFace; } if (group->next !=NULL) cleanupGroup (group->next); return true; } /** \brief Cleans up all Faces starting from face until NULL is reached. \param face the first face to clean. */ bool Model::cleanupFace (Face* face) { PRINTF(3)("Cleaning up Face\n"); if (face->materialString != NULL) delete []face->materialString; if (face->firstElem != NULL) { this->cleanupFaceElement(face->firstElem); delete face->firstElem; } if (face->next != NULL) { this->cleanupFace (face->next); delete face->next; } } /** \brief Cleans up all FaceElements starting from faceElem. \param faceElem the first FaceElement to clean. */ bool Model::cleanupFaceElement(FaceElement* faceElem) { if (faceElem->next != NULL) { this->cleanupFaceElement (faceElem->next); delete faceElem->next; } } /** \brief parses a group String \param groupString the new Group to create This function initializes a new Group. With it you should be able to import .obj-files with more than one Models inside. */ bool Model::addGroup (char* groupString) { PRINTF(3)("Read Group: %s.\n", groupString); if (this->groupCount != 0 && this->currentGroup->faceCount>0) { // finalizeGroup(currentGroup); this->currentGroup = this->currentGroup->next = new Group; this->initGroup(this->currentGroup); } // setting the group name if not default. if (strcmp(groupString, "default")) { this->currentGroup->name = new char [strlen(groupString)+1]; strcpy(this->currentGroup->name, groupString); } ++this->groupCount; } /** \brief parses a vertex-String \param vertexString The String that will be parsed. If a vertex line is found this function will inject it into the vertex-Array */ bool Model::addVertex (char* vertexString) { float subbuffer1; float subbuffer2; float subbuffer3; sscanf (vertexString, "%f %f %f", &subbuffer1, &subbuffer2, &subbuffer3); PRINTF(3)("reading in a vertex: %f %f %f\n", &subbuffer1, &subbuffer2, &subbuffer3); this->vertices->addEntry(subbuffer1*scaleFactor, subbuffer2*scaleFactor, subbuffer3*scaleFactor); return true; } /** \brief parses a vertex-String \param x the X-coordinate of the Vertex to add. \param y the Y-coordinate of the Vertex to add. \param z the Z-coordinate of the Vertex to add. */ bool Model::addVertex(const float x, const float y, const float z) { PRINTF(4)("reading in a vertex: %f %f %f\n", x, y, z); this->vertices->addEntry(x*scaleFactor, y*scaleFactor, z*scaleFactor); return true; } /** \brief parses a face-string \param faceString The String that will be parsed. If a face line is found this function will add it to the glList. */ bool Model::addFace (char* faceString) { if (this->currentGroup->faceCount >0) this->currentGroup->currentFace = this->currentGroup->currentFace->next = new Face; this->initFace (this->currentGroup->currentFace); FaceElement* tmpElem = this->currentGroup->currentFace->firstElem = new FaceElement; tmpElem->next = NULL; while(strcmp (faceString, "\0")) { if (this->currentGroup->currentFace->vertexCount>0) tmpElem = tmpElem->next = new FaceElement; tmpElem->next = NULL; char tmpValue [50]; int tmpLen; char* vertex = NULL; char* texture = NULL; char* normal = NULL; sscanf (faceString, "%s", tmpValue); tmpLen = strlen(tmpValue); vertex = tmpValue; if ((texture = strstr (vertex, "/")) != NULL) { texture[0] = '\0'; texture ++; if ((normal = strstr (texture, "/")) !=NULL) { normal[0] = '\0'; normal ++; } } if (vertex) tmpElem->vertexNumber = atoi(vertex)-1; else tmpElem->vertexNumber = -1; if (texture) tmpElem->texCoordNumber = atoi(texture)-1; else tmpElem->texCoordNumber = -1; if (normal) tmpElem->normalNumber = atoi(normal)-1; else tmpElem->normalNumber = -1; faceString += tmpLen; if (strcmp (faceString, "\0")) faceString++; this->currentGroup->currentFace->vertexCount++; } this->currentGroup->faceCount += this->currentGroup->currentFace->vertexCount -2; } /** \brief adds a new Face \param faceElemCount the number of Vertices to add to the Face. \param type 0: vertex only, 1: vertex and normal, 2: vertex and Texture, 3 vertex, normal and texture */ bool Model::addFace(const float faceElemCount, int type, ...) { if (this->currentGroup->faceCount > 0) this->currentGroup->currentFace = this->currentGroup->currentFace->next = new Face; this->initFace (this->currentGroup->currentFace); FaceElement* tmpElem = this->currentGroup->currentFace->firstElem = new FaceElement; tmpElem->next = NULL; va_list itemlist; va_start (itemlist, type); for (int i = 0; i < faceElemCount; i++) { if (this->currentGroup->currentFace->vertexCount>0) tmpElem = tmpElem->next = new FaceElement; tmpElem->next = NULL; tmpElem->vertexNumber = va_arg (itemlist, int) -1; if (type >= 2) tmpElem->texCoordNumber = va_arg (itemlist, int) -1; if (type == 1 || type ==3) tmpElem->normalNumber = va_arg(itemlist, int) -1; this->currentGroup->currentFace->vertexCount++; } va_end(itemlist); this->currentGroup->faceCount += this->currentGroup->currentFace->vertexCount - 2; } /** \brief parses a vertexNormal-String \param normalString The String that will be parsed. If a vertexNormal line is found this function will inject it into the vertexNormal-Array */ bool Model::addVertexNormal (char* normalString) { float subbuffer1; float subbuffer2; float subbuffer3; sscanf (normalString, "%f %f %f", &subbuffer1, &subbuffer2, &subbuffer3); PRINTF(3)("found vertex-Normal %f, %f, %f\n", &subbuffer1,&subbuffer2,&subbuffer3); this->normals->addEntry(subbuffer1, subbuffer2, subbuffer3); return true; } /** \brief adds a VertexNormal. \param x The x coordinate of the Normal. \param y The y coordinate of the Normal. \param z The z coordinate of the Normal. If a vertexNormal line is found this function will inject it into the vertexNormal-Array */ bool Model::addVertexNormal(const float x, const float y, const float z) { PRINTF(3)("found vertex-Normal %f, %f, %f\n", x, y, z); this->normals->addEntry(x, y, z); } /** \brief parses a vertexTextureCoordinate-String \param vTextureString The String that will be parsed. If a vertexTextureCoordinate line is found, this function will inject it into the vertexTexture-Array */ bool Model::addVertexTexture (char* vTextureString) { float subbuffer1; float subbuffer2; sscanf (vTextureString, "%f %f", &subbuffer1, &subbuffer2); PRINTF(3)("found vertex-Texture %f, %f\n", &subbuffer1, &subbuffer2); this->vTexture->addEntry(subbuffer1); this->vTexture->addEntry(subbuffer2); return true; } /** \brief adds a Texture Coordinate \param u The u coordinate of the TextureCoordinate. \param v The y coordinate of the TextureCoordinate. If a TextureCoordinate line is found this function will inject it into the TextureCoordinate-Array */ bool Model::addVertexTexture(const float u, const float v) { PRINTF(3)("found vertex-Texture %f, %f\n", u, v); this->vTexture->addEntry(u); this->vTexture->addEntry(v); } /** \brief Function that selects a material, if changed in the obj file. \param matString the Material that will be set. */ bool Model::addUseMtl (char* matString) { /* if (!this->mtlFileName) { PRINTF(4)("Not using new defined material, because no mtlFile found yet\n"); return false; } */ if (this->currentGroup->faceCount >0) this->currentGroup->currentFace = this->currentGroup->currentFace->next = new Face; this->initFace (this->currentGroup->currentFace); this->currentGroup->currentFace->materialString = new char[strlen(matString)+1]; strcpy (this->currentGroup->currentFace->materialString, matString); if (this->currentGroup->faceCount == 0) this->currentGroup->faceCount ++; } /** \brief reads and includes the Faces/Materials into the openGL state Machine */ bool Model::importToGL (void) { // finalize the Arrays this->vertices->finalizeArray(); this->vTexture->finalizeArray(); if (normals->getCount() == 0) // vertices-Array must be built for this this->buildVertexNormals(); this->normals->finalizeArray(); this->currentGroup = this->firstGroup; while (this->currentGroup != NULL) { // creating a glList for the Group if ((this->currentGroup->listNumber = glGenLists(1)) == 0) { PRINTF(1)("list could not be created for this Model\n"); return false; } glNewList (this->currentGroup->listNumber, GL_COMPILE); // Putting Faces to GL Face* tmpFace = this->currentGroup->firstFace; while (tmpFace != NULL) { if (tmpFace->vertexCount == 0 && tmpFace->materialString != NULL) { if (this->currentGroup->faceMode != -1) glEnd(); this->currentGroup->faceMode = 0; Material* tmpMat; if ((tmpMat = material->search(tmpFace->materialString)) != NULL) { tmpMat->select(); PRINTF(2)("using material %s for coming Faces.\n", tmpFace->materialString); } else PRINTF(1)("material %s not found.\n", tmpFace->materialString); } else if (tmpFace->vertexCount == 3) { if (this->currentGroup->faceMode != 3) { if (this->currentGroup->faceMode != -1) glEnd(); glBegin(GL_TRIANGLES); } this->currentGroup->faceMode = 3; PRINTF(3)("found triag.\n"); } else if (tmpFace->vertexCount == 4) { if (this->currentGroup->faceMode != 4) { if (this->currentGroup->faceMode != -1) glEnd(); glBegin(GL_QUADS); } this->currentGroup->faceMode = 4; PRINTF(3)("found quad.\n"); } else if (tmpFace->vertexCount > 4) { if (this->currentGroup->faceMode != -1) glEnd(); glBegin(GL_POLYGON); PRINTF(3)("Polygon with %i faces found.", tmpFace->vertexCount); this->currentGroup->faceMode = tmpFace->vertexCount; } FaceElement* tmpElem = tmpFace->firstElem; while (tmpElem != NULL) { // PRINTF(2)("%s\n", tmpElem->value); this->addGLElement(tmpElem); tmpElem = tmpElem->next; } tmpFace = tmpFace->next; } glEnd(); glEndList(); this->currentGroup = this->currentGroup->next; } } /** \brief Adds a Face-element (one vertex of a face) with all its information. \param elem The FaceElement to add to the OpenGL-environment. It does this by searching: 1. The Vertex itself 2. The VertexNormale 3. The VertexTextureCoordinate merging this information, the face will be drawn. */ bool Model::addGLElement (FaceElement* elem) { PRINTF(3)("importing grafical Element to openGL.\n"); if (elem->texCoordNumber != -1) glTexCoord2fv(this->vTexture->getArray() + elem->texCoordNumber * 2); if (elem->normalNumber != -1) glNormal3fv(this->normals->getArray() + elem->normalNumber * 3); if (elem->vertexNumber != -1) glVertex3fv(this->vertices->getArray() + elem->vertexNumber * 3); } /** \brief A routine that is able to create normals. The algorithm does the following: 1. It calculates creates Vectors for each normale, and sets them to zero. 2. It then Walks through a) all the Groups b) all the Faces c) all the FaceElements 3. It searches for a points two neighbours per Face, takes Vecotrs to them calculates FaceNormals and adds it to the Points Normal. 4. It goes through all the normale-Points and calculates the VertexNormale and includes it in the normals-Array. */ bool Model::buildVertexNormals () { PRINTF(2)("Normals are being calculated.\n"); Vector* normArray = new Vector [vertices->getCount()/3]; for (int i=0; igetCount()/3;i++) normArray[i] = Vector(.0,.0,.0); int firstTouch; int secondTouch; Vector prevV; Vector nextV; Vector curV; Group* tmpGroup = firstGroup; while (tmpGroup) { Face* tmpFace = tmpGroup->firstFace; while (tmpFace) { if (tmpFace->firstElem) { FaceElement* firstElem = tmpFace->firstElem; FaceElement* prevElem; FaceElement* curElem = firstElem; FaceElement* nextElem; FaceElement* lastElem; // find last Element of the Chain. !! IMPORTANT:the last Element of the Chain must point to NULL, or it will resolv into an infinity-loop. while (curElem) { prevElem = curElem; curElem = curElem->next; } lastElem = prevElem; curElem = firstElem; for (int j=0; jvertexCount; j++) { if (!(nextElem = curElem->next)) nextElem = firstElem; curElem->normalNumber = curElem->vertexNumber; curV = Vector (vertices->getArray()[curElem->vertexNumber*3], vertices->getArray()[curElem->vertexNumber*3+1], vertices->getArray()[curElem->vertexNumber*3+2]); prevV = Vector (vertices->getArray()[prevElem->vertexNumber*3], vertices->getArray()[prevElem->vertexNumber*3+1], vertices->getArray()[prevElem->vertexNumber*3+2]) - curV; nextV = Vector (vertices->getArray()[nextElem->vertexNumber*3], vertices->getArray()[nextElem->vertexNumber*3+1], vertices->getArray()[nextElem->vertexNumber*3+2]) - curV; normArray[curElem->vertexNumber] = normArray[curElem->vertexNumber] + nextV.cross(prevV); prevElem = curElem; curElem = curElem->next; } } tmpFace = tmpFace->next; } tmpGroup = tmpGroup->next; } for (int i=0; igetCount()/3;i++) { normArray[i].normalize(); PRINTF(3)("Found Normale number %d: (%f; %f, %f).\n", i, normArray[i].x, normArray[i].y, normArray[i].z); this->normals->addEntry(normArray[i].x, normArray[i].y, normArray[i].z); } delete []normArray; } /** \brief Includes a default model This will inject a Cube, because this is the most basic model. */ void Model::cubeModel(void) { this->addVertex ("-0.5 -0.5 0.5"); this->addVertex ("0.5 -0.5 0.5"); this->addVertex ("-0.5 0.5 0.5"); this->addVertex ("0.5 0.5 0.5"); this->addVertex ("-0.5 0.5 -0.5"); this->addVertex ("0.5 0.5 -0.5"); this->addVertex ("-0.5 -0.5 -0.5"); this->addVertex ("0.5 -0.5 -0.5"); this->addVertexTexture ("0.0 0.0"); this->addVertexTexture ("1.0 0.0"); this->addVertexTexture ("0.0 1.0"); this->addVertexTexture ("1.0 1.0"); this->addVertexTexture ("0.0 2.0"); this->addVertexTexture ("1.0 2.0"); this->addVertexTexture ("0.0 3.0"); this->addVertexTexture ("1.0 3.0"); this->addVertexTexture ("0.0 4.0"); this->addVertexTexture ("1.0 4.0"); this->addVertexTexture ("2.0 0.0"); this->addVertexTexture ("2.0 1.0"); this->addVertexTexture ("-1.0 0.0"); this->addVertexTexture ("-1.0 1.0"); this->addVertexNormal ("0.0 0.0 1.0"); this->addVertexNormal ("0.0 0.0 1.0"); this->addVertexNormal ("0.0 0.0 1.0"); this->addVertexNormal ("0.0 0.0 1.0"); this->addVertexNormal ("0.0 1.0 0.0"); this->addVertexNormal ("0.0 1.0 0.0"); this->addVertexNormal ("0.0 1.0 0.0"); this->addVertexNormal ("0.0 1.0 0.0"); this->addVertexNormal ("0.0 0.0 -1.0"); this->addVertexNormal ("0.0 0.0 -1.0"); this->addVertexNormal ("0.0 0.0 -1.0"); this->addVertexNormal ("0.0 0.0 -1.0"); this->addVertexNormal ("0.0 -1.0 0.0"); this->addVertexNormal ("0.0 -1.0 0.0"); this->addVertexNormal ("0.0 -1.0 0.0"); this->addVertexNormal ("0.0 -1.0 0.0"); this->addVertexNormal ("1.0 0.0 0.0"); this->addVertexNormal ("1.0 0.0 0.0"); this->addVertexNormal ("1.0 0.0 0.0"); this->addVertexNormal ("1.0 0.0 0.0"); this->addVertexNormal ("-1.0 0.0 0.0"); this->addVertexNormal ("-1.0 0.0 0.0"); this->addVertexNormal ("-1.0 0.0 0.0"); this->addVertexNormal ("-1.0 0.0 0.0"); /* normaleLess-testingMode this->addFace ("1 2 4 3"); this->addFace ("3 4 6 5"); this->addFace ("5 6 8 7"); this->addFace ("7 8 2 1"); this->addFace ("2 8 6 4"); this->addFace ("7 1 3 5"); */ this->addFace ("1/1/1 2/2/2 4/4/3 3/3/4"); this->addFace ("3/3/5 4/4/6 6/6/7 5/5/8"); this->addFace ("5/5/9 6/6/10 8/8/11 7/7/12"); this->addFace ("7/7/13 8/8/14 2/10/15 1/9/16"); this->addFace ("2/2/17 8/11/18 6/12/19 4/4/20"); this->addFace ("7/13/21 1/1/22 3/3/23 5/14/24"); } void Model::sphereModel() { int detail = 30; if (detail <= 0) detail = 1; float df = (float)detail; for (float i = 0.0; i < df/2; i+=1.0) { for (float j = 0.0; j < df; j+=1.0) { float vz = i/df *2.0*PI - PI/2.0; this->addVertex(cos(j/df*2.0*PI) * cos(vz) , sin(j/df*2.0*PI) * cos(vz), sin(vz)); //if (j==0.0) //printf ("%f %f\n", vz, sin (vz)); if (i==0.0) printf("%f, %f\n", j/df*2.0*PI, cos(j/df*PI)); } } vertices->debug(); for (int i = 0; i < detail/2; i++) for (int j = 1; j < detail; j++) { unsigned int v1,v2,v3,v4; v1 = i*detail +j; /* if (j+1 == detail) { v2 = i*detail +1; v3 = i*detail+detail + 1; } else*/ { v2 = i*detail +j+1; v3 = i*detail+detail + j+1; } v4 = i*detail+detail + j; //printf("%i %i %i %i\n", v1, v2, v3, v4); this->addFace(4, 0, v1, v2, v3, v4); } } /** \brief Creates a Cylinder. */ void Model::cylinderModel(void) { unsigned int detail = 20; float size = 1.0; // check if devision by zero if (detail <= 3) detail = 3; int count = 0; // defining Points of the Cylinder. for (float phi = 0.0; phi < 2.0*PI; phi += 2.0*PI/(float)detail) { this->addVertex(size*cos(phi), size*sin(phi), -size); this->addVertex(size*cos(phi), size*sin(phi), size); count ++; } this->addVertex(0, 0, -size); this->addVertex(0, 0, size); if (count != detail) cout << "calculation error, count should be " << detail << " but is " << count << endl; vertices->debug(); // adding Faces for (int i = 0; i < detail-1; i++) { int p1, p2, p3, p4; p1 = 2*i+1; p2 = 2*i+2; if (i <= detail); p3 = 2*i+4; p4 = 2*i+3; cout <addFace(4, 0, p1, p2, p3, p4); this->addFace(3, 0, p4, p1, 2*detail+1); this->addFace(3, 0, p2, p3, 2*detail+2); } addFace(4,0, 2*detail-1, 2*detail, 2, 1); this->addFace(3, 0, 1, 2*detail-1, 2*detail+1); this->addFace(3, 0, 2*detail, 2, 2*detail+2); }