/* 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: ... 2005-07-06: (Patrick) added new function buildTriangleList() */ #define DEBUG_SPECIAL_MODULE DEBUG_MODULE_IMPORTER #include "static_model_data.h" #include "debug.h" #include #include //////////////////// /// SUB-Elements /// //////////////////// /** * @brief creates a new ModelFaceElement */ StaticModelData::FaceElement::FaceElement() { this->vertexNumber = -1; this->normalNumber = -1; this->texCoordNumber = -1; } /** * @brief creates a new ModelFace */ StaticModelData::Face::Face() { this->_material = NULL; } /** * @brief Creates a new ModelGroup */ StaticModelData::Group::Group() { PRINTF(4)("Adding new Group\n"); this->name = ""; this->faceMode = -1; this->listNumber = 0; this->indices = NULL; } /** * @brief deletes a ModelGroup */ StaticModelData::Group::~Group() { PRINTF(5)("Cleaning up group\n"); // deleting the glList if (this->listNumber != 0) glDeleteLists(this->listNumber, 1); } /** * @brief cleans up a ModelGroup * * actually does the same as the delete Operator, but does not delete the predecessing group */ void StaticModelData::Group::cleanup() { PRINTF(5)("Cleaning up group\n"); this->_faces.clear(); } ///////////// /// MODEL /// ///////////// ObjectListDefinition(StaticModelData); /** * @brief Creates a 3D-Model. * * assigns it a Name and a Type */ StaticModelData::StaticModelData(const std::string& modelName) { this->registerObject(this, StaticModelData::_objectList); PRINTF(4)("new 3D-Model is being created\n"); this->setName(modelName); this->finalized = false; // setting the start group; this->_modelGroups.push_back(Group()); this->faceCount = 0; this->scaleFactor = 1.0f; } /** * @brief deletes an Model. * * Looks if any from model allocated space is still in use, and if so deleted it. */ StaticModelData::~StaticModelData() { PRINTF(4)("Deleting Model %s\n", this->getCName()); this->cleanup(); } /** * @brief Finalizes an Object. This can be done outside of the Class. */ void StaticModelData::finalize() { // this creates the display List. this->importToDisplayList(); this->buildTriangleList(); this->finalized = true; } /** * @brief rebuild the Model from the Information we got. */ void StaticModelData::rebuild() { PRINTF(3)("Rebuilding Model '%s'\n", this->getCName()); this->finalize(); } ////////// // DRAW // ////////// /** * @brief Draws the Models of all Groups. * * It does this by just calling the Lists that must have been created earlier. */ void StaticModelData::draw () const { PRINTF(4)("drawing the 3D-Models\n"); for(unsigned int i = 0; i < _modelGroups.size(); ++i ) { PRINTF(5)("Drawing model %s\n", _modelGroups[i].name.c_str()); glCallList (_modelGroups[i].listNumber); } } /** * @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 StaticModelData::draw (unsigned int groupNumber) const { if (unlikely(groupNumber >= _modelGroups.size())) { PRINTF(2)("You requested model number %i, but this File only contains of %i Models.\n", groupNumber-1, _modelGroups.size()); return; } else { PRINTF(4)("Drawing model number %i named %s\n", groupNumber, _modelGroups[groupNumber].name.c_str()); glCallList (_modelGroups[groupNumber].listNumber); } } /** * @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 StaticModelData::draw (const std::string& groupName) const { PRINTF(4)("drawing the requested 3D-Models if found.\n"); std::vector::const_iterator it = std::find(_modelGroups.begin(), _modelGroups.end(), groupName); if (it != _modelGroups.end()) { PRINTF(4)("Drawing model %s\n", (*it).name.c_str()); glCallList ((*it).listNumber); } else { PRINTF(2)("Model Named %s in %s not Found.\n", groupName.c_str(), this->getCName()); } } ////////// // INIT // ////////// /** * @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 StaticModelData::cleanup() { PRINTF(4)("cleaning up the 3D-Model to save Memory.\n"); for (unsigned int i = 0; i < _modelGroups.size(); ++i) _modelGroups[i].cleanup(); return true; } ////////// // MESH // ////////// /** * @brief adds a new Material to the Material List * @param material the Material to add * @returns the added material * * this also tells this Model, that all the Materials are handled externally * with this option set the Materials will not be deleted with the Model. */ Material* StaticModelData::addMaterial(const Material& material) { this->materialList.push_back(material); return &materialList.back(); } /** * @brief adds a new Material to the Material List * @param materialName the name of the Material to add * @returns the added material */ Material* StaticModelData::addMaterial(const std::string& materialName) { // adding material to the List of materials this->materialList.push_back(Material(materialName)); return &materialList.back(); } /** * @brief finds a Material by its name and returns it * @param materialName the Name of the material to search for. * @returns the Material if found, NULL otherwise */ Material* StaticModelData::findMaterialByName(const std::string& materialName) { std::list::iterator it = std::find(materialList.begin(), materialList.end(), materialName); if (it != materialList.end()) return &(*it); else return NULL; } /** * @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 create Models with more than one SubModel inside */ bool StaticModelData::addGroup(const std::string& groupString) { PRINTF(4)("Read Group: %s.\n", groupString.c_str()); if (!_modelGroups.empty() && !_modelGroups.back()._faces.empty()) _modelGroups.push_back(Group()); _modelGroups.back().name = groupString; return true; } /** * @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 StaticModelData::addVertex (const std::string& vertexString) { float subbuffer1; float subbuffer2; float subbuffer3; sscanf (vertexString.c_str(), "%f %f %f", &subbuffer1, &subbuffer2, &subbuffer3); this->vertices.push_back(subbuffer1*scaleFactor); this->vertices.push_back(subbuffer2*scaleFactor); this->vertices.push_back(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 StaticModelData::addVertex(float x, float y, float z) { PRINTF(5)("reading in a vertex: %f %f %f\n", x, y, z); this->vertices.push_back(x*scaleFactor); this->vertices.push_back(y*scaleFactor); this->vertices.push_back(z*scaleFactor); return true; } /** * @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 StaticModelData::addVertexNormal (const std::string& normalString) { float subbuffer1; float subbuffer2; float subbuffer3; sscanf (normalString.c_str(), "%f %f %f", &subbuffer1, &subbuffer2, &subbuffer3); this->normals.push_back(subbuffer1); this->normals.push_back(subbuffer2); this->normals.push_back(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 StaticModelData::addVertexNormal(float x, float y, float z) { PRINTF(5)("found vertex-Normal %f, %f, %f\n", x, y, z); this->normals.push_back(x); this->normals.push_back(y); this->normals.push_back(z); return true; } /** * @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 * * !! WARNING THIS IS DIFFERNT FROM addVervexTexture(float, float); because it changes the second entry to 1-v !! */ bool StaticModelData::addVertexTexture (const std::string& vTextureString) { float subbuffer1; float subbuffer2; sscanf (vTextureString.c_str(), "%f %f", &subbuffer1, &subbuffer2); this->vTexture.push_back(subbuffer1); this->vTexture.push_back(1 - 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 StaticModelData::addVertexTexture(float u, float v) { PRINTF(5)("found vertex-Texture %f, %f\n", u, v); this->vTexture.push_back(u); this->vTexture.push_back(v); 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. * * String is different from the argument addFace, * in this, that the first Vertex/Normal/Texcoord is 1 instead of 0 * * @TODO make it std::string conform */ bool StaticModelData::addFace (const std::string& faceStringInput) { const char* faceString = faceStringInput.c_str(); Face newFace; while(strcmp (faceString, "\0")) { FaceElement newElem; 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) newElem.vertexNumber = atoi(vertex)-1; if (texture) newElem.texCoordNumber = atoi(texture)-1; if (normal) newElem.normalNumber = atoi(normal)-1; faceString += tmpLen; if (strcmp (faceString, "\0")) faceString++; newFace._elements.push_back(newElem); } //this->currentGroup->faceCount += this->currentGroup->currentFace->vertexCount -2; _modelGroups.back()._faces.push_back(newFace); this->faceCount += newFace._elements.size() - 2; return true; } /** * @brief adds a new Face * @param faceElemCount the number of Vertices to add to the Face. * @param type The information Passed with each Vertex */ bool StaticModelData::addFace(int faceElemCount, VERTEX_FORMAT type, va_list args) { Face newFace; for (int i = 0; i < faceElemCount; i++) { FaceElement newElem; newElem.vertexNumber = va_arg (args, int); if (type & TEXCOORD) newElem.texCoordNumber = va_arg (args, int); if (type & NORMAL) newElem.normalNumber = va_arg(args, int); newFace._elements.push_back(newElem); } //this->currentGroup->faceCount += this->currentGroup->currentFace->vertexCount -2; _modelGroups.back()._faces.push_back(newFace); this->faceCount += newFace._elements.size() - 2; return true; } /** * Function that selects a material, if changed in the obj file. * @param matString the Material that will be set. */ bool StaticModelData::setMaterial(const std::string& matString) { Face matFace; matFace._material = this->findMaterialByName(matString); _modelGroups.back()._faces.push_back(matFace); return true; } /** * Function that selects a material, if changed in the obj file. * @param mtl the Material that will be set. */ bool StaticModelData::setMaterial(Material* mtl) { Face matFace; matFace._material = mtl; _modelGroups.back()._faces.push_back(matFace); return true; } /** * @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 StaticModelData::buildVertexNormals () { PRINTF(4)("Normals are being calculated.\n"); Vector* normArray = new Vector [vertices.size()/3]; for (unsigned int i=0; i::iterator group = _modelGroups.begin(); group != _modelGroups.end(); ++group) { for (std::vector::iterator face = (*group)._faces.begin(); face != (*group)._faces.end(); ++face) { if (!(*face)._elements.empty()) { std::vector::iterator firstElem = (*face)._elements.begin(); std::vector::iterator prevElem; std::vector::iterator curElem = firstElem; std::vector::iterator nextElem; std::vector::iterator lastElem; // find last Element of the Chain. while (curElem != (*face)._elements.end()) { prevElem = curElem; ++curElem; } lastElem = prevElem; curElem = firstElem; for (unsigned int j = 0; j < (*face)._elements.size(); j++) { nextElem = curElem; nextElem++; if (nextElem == (*face)._elements.end()) nextElem = firstElem; (*curElem).normalNumber = (*curElem).vertexNumber; curV = Vector (this->vertices[(*curElem).vertexNumber*3], this->vertices[(*curElem).vertexNumber*3+1], this->vertices[(*curElem).vertexNumber*3+2]); prevV = Vector (this->vertices[(*prevElem).vertexNumber*3], this->vertices[(*prevElem).vertexNumber*3+1], this->vertices[(*prevElem).vertexNumber*3+2]) - curV; nextV = Vector (this->vertices[(*nextElem).vertexNumber*3], this->vertices[(*nextElem).vertexNumber*3+1], this->vertices[(*nextElem).vertexNumber*3+2]) - curV; normArray[(*curElem).vertexNumber] = normArray[(*curElem).vertexNumber] + nextV.cross(prevV); prevElem = curElem; ++curElem; } } } } for (unsigned int i=0; i < this->vertices.size()/3;i++) { normArray[i].normalize(); PRINTF(5)("Found Normale number %d: (%f; %f, %f).\n", i, normArray[i].x, normArray[i].y, normArray[i].z); this->addVertexNormal(normArray[i].x, normArray[i].y, normArray[i].z); } delete[] normArray; return true; } //////////// // openGL // //////////// /** * reads and includes the Faces/Materials into the openGL state Machine */ bool StaticModelData::importToDisplayList() { // finalize the Arrays if (normals.size() == 0) // vertices-Array must be built for this this->buildVertexNormals(); for (std::vector::iterator group = _modelGroups.begin(); group != _modelGroups.end(); ++group) { // creating a glList for the Group if (((*group).listNumber = glGenLists(1)) == 0) { PRINTF(2)("glList could not be created for this Model\n"); return false; } glNewList ((*group).listNumber, GL_COMPILE); // Putting Faces to GL for (std::vector::const_iterator face = (*group)._faces.begin(); face != (*group)._faces.end(); ++face) { if ((*face)._elements.empty() && (*face)._material != NULL) { if ((*group).faceMode != -1) glEnd(); (*group).faceMode = 0; if ((*face)._material != NULL) { (*face)._material->select(); PRINTF(5)("using material %s for coming Faces.\n", (*face)._material->getCName()); } } else if ((*face)._elements.size() == 3) { if ((*group).faceMode != 3) { if ((*group).faceMode != -1) glEnd(); glBegin(GL_TRIANGLES); } (*group).faceMode = 3; PRINTF(5)("found triag.\n"); } else if ((*face)._elements.size() == 4) { if ((*group).faceMode != 4) { if ((*group).faceMode != -1) glEnd(); glBegin(GL_QUADS); } (*group).faceMode = 4; PRINTF(5)("found quad.\n"); } else if ((*face)._elements.size() > 4) { if ((*group).faceMode != -1) glEnd(); glBegin(GL_POLYGON); PRINTF(5)("Polygon with %i faces found.", (*face)._elements.size()); (*group).faceMode = (*face)._elements.size(); } for (std::vector::const_iterator elem = (*face)._elements.begin(); elem != (*face)._elements.end(); ++elem) { // PRINTF(2)("%s\n", (*elem).value); this->addGLElement(*elem); } } glEnd(); glEndList(); } return true; } /** * builds an array of triangles, that can later on be used for obb separation and octree separation */ bool StaticModelData::buildTriangleList() { if( unlikely(!this->triangles.empty())) return true; /* make sure, that all the arrays are finalized */ if( normals.size() == 0) // vertices-Array must be built for this this->buildVertexNormals(); int index = 0; //!< the counter for the triangle array unsigned int numTriangles = 0; bool warned = false; /* count the number of triangles */ /* now iterate through all groups and build up the triangle list */ for (std::vector::iterator group = _modelGroups.begin(); group != _modelGroups.end(); ++group) { for (std::vector::const_iterator face = (*group)._faces.begin(); face != (*group)._faces.end(); ++face) { /* if its a triangle just add it to the list */ if( (*face)._elements.size() == 3) { ++numTriangles; } /* if the polygon is a quad */ else if((*face)._elements.size() == 4) { numTriangles += 2; } else if( (*face)._elements.size() > 4) { if (!warned) { PRINTF(2)("This model (%s) got over 4 vertices per face <=> conflicts in the CD engine!\n", this->getCName()); warned = true; } } } } PRINTF(3)("got %i triangles, %i vertices\n", numTriangles, this->vertices.size()); /* write MODELINFO structure */ /* allocate memory for the new triangle structures */ this->triangles.resize(numTriangles); /* now iterate through all groups and build up the triangle list */ for (std::vector::iterator group = _modelGroups.begin(); group != _modelGroups.end(); ++group) { for (std::vector::const_iterator face = (*group)._faces.begin(); face != (*group)._faces.end(); ++face) { /* if its a triangle just add it to the list */ if( (*face)._elements.size() == 3) { unsigned int j = 0; for (std::vector::const_iterator elem = (*face)._elements.begin(); elem != (*face)._elements.end(); ++elem) { this->triangles[index].indexToVertices[j] = (unsigned int)(*elem).vertexNumber * 3; this->triangles[index].indexToNormals[j] = (unsigned int)(*elem).normalNumber * 3; this->triangles[index].indexToTexCoor[j] = (unsigned int)(*elem).texCoordNumber * 3; ++j; } ++index; } /* if the polygon is a quad */ else if( (*face)._elements.size() == 4) { std::vector::const_iterator elem = (*face)._elements.begin(); this->triangles[index].indexToVertices[0] = (unsigned int)(*elem).vertexNumber * 3; this->triangles[index].indexToNormals[0] = (unsigned int)(*elem).normalNumber * 3; this->triangles[index].indexToTexCoor[0] = (unsigned int)(*elem).texCoordNumber * 3; this->triangles[index + 1].indexToVertices[0] = (unsigned int)(*elem).vertexNumber * 3; this->triangles[index + 1].indexToNormals[0] = (unsigned int)(*elem).normalNumber * 3; this->triangles[index + 1].indexToTexCoor[0] = (unsigned int)(*elem).texCoordNumber * 3; ++elem; this->triangles[index].indexToVertices[1] = (unsigned int)(*elem).vertexNumber * 3; this->triangles[index].indexToNormals[1] = (unsigned int)(*elem).normalNumber * 3; this->triangles[index].indexToTexCoor[1] = (unsigned int)(*elem).texCoordNumber * 3; ++elem; this->triangles[index].indexToVertices[2] = (unsigned int)(*elem).vertexNumber * 3; this->triangles[index].indexToNormals[2] = (unsigned int)(*elem).normalNumber * 3; this->triangles[index].indexToTexCoor[2] = (unsigned int)(*elem).texCoordNumber * 3; this->triangles[index + 1].indexToVertices[2] = (unsigned int)(*elem).vertexNumber * 3; this->triangles[index + 1].indexToNormals[2] = (unsigned int)(*elem).normalNumber * 3; this->triangles[index + 1].indexToTexCoor[2] = (unsigned int)(*elem).texCoordNumber * 3; ++elem; this->triangles[index + 1].indexToVertices[1] = (unsigned int)(*elem).vertexNumber * 3; this->triangles[index + 1].indexToNormals[1] = (unsigned int)(*elem).normalNumber * 3; this->triangles[index + 1].indexToTexCoor[1] = (unsigned int)(*elem).texCoordNumber * 3; index += 2; } } } return true; } /** * 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 StaticModelData::addGLElement (const FaceElement& elem) { PRINTF(5)("importing grafical Element to openGL.\n"); if (elem.texCoordNumber > -1) { if (likely((unsigned int)elem.texCoordNumber < this->vTexture.size())) glTexCoord2fv(&this->vTexture[0] + elem.texCoordNumber * 2); else PRINTF(2)("TextureCoordinate %d is not in the List (max: %d)\nThe Model might be incomplete\n", elem.texCoordNumber, this->vTexture.size()); } if (elem.normalNumber > -1) { if (likely((unsigned int)elem.normalNumber < this->normals.size())) glNormal3fv(&this->normals[0] + elem.normalNumber * 3); else PRINTF(2)("Normal %d is not in the List (max: %d)\nThe Model might be incomplete", elem.normalNumber, this->normals.size()); } if (elem.vertexNumber > -1) { if (likely((unsigned int)elem.vertexNumber < this->vertices.size())) glVertex3fv(&this->vertices[0]+ elem.vertexNumber * 3); else PRINTF(2)("Vertex %d is not in the List (max: %d)\nThe Model might be incomplete", elem.vertexNumber, this->vertices.size()); } return true; }