/* 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 //////////////////// /// SUB-Elements /// //////////////////// /** * @brief creates a new ModelFaceElement */ ModelFaceElement::ModelFaceElement() { this->vertexNumber = -1; this->normalNumber = -1; this->texCoordNumber = -1; this->next = NULL; } /** * @brief destroys a ModelFaceElement */ ModelFaceElement::~ModelFaceElement() { if (this->next) delete this->next; } /** * @brief creates a new ModelFace */ ModelFace::ModelFace() { this->vertexCount = 0; this->firstElem = NULL; this->material = NULL; this->next = NULL; } /** * deletes a ModelFace */ ModelFace::~ModelFace() { PRINTF(5)("Cleaning up Face\n"); if (this->firstElem != NULL) delete this->firstElem; if (this->next != NULL) delete this->next; } /** * @brief Creates a new ModelGroup */ ModelGroup::ModelGroup() { PRINTF(4)("Adding new Group\n"); this->name = ""; this->faceMode = -1; this->faceCount = 0; this->next = NULL; this->listNumber = 0; this->indices = NULL; this->firstFace = new ModelFace; this->currentFace = this->firstFace; } /** * @brief deletes a ModelGroup */ ModelGroup::~ModelGroup() { PRINTF(5)("Cleaning up group\n"); if (this->firstFace != NULL) delete this->firstFace; // deleting the glList if (this->listNumber != 0) glDeleteLists(this->listNumber, 1); if (this->next !=NULL) delete this->next; } /** * @brief cleans up a ModelGroup * * actually does the same as the delete Operator, but does not delete the predecessing group */ void ModelGroup::cleanup() { PRINTF(5)("Cleaning up group\n"); if (this->firstFace) delete this->firstFace; this->firstFace = NULL; if (this->next) this->next->cleanup(); } ///////////// /// 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->currentGroup = this->firstGroup = new ModelGroup; this->groupCount = 0; 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 "); if (!this->getName().empty()) { PRINT(4)("%s\n", this->getCName()); } else { PRINT(4)("\n"); } this->cleanup(); PRINTF(5)("Deleting display Lists.\n"); delete this->firstGroup; // deleting the MaterialList PRINTF(5)("Deleting Materials.\n"); //! @todo do we really have to delete this material?? std::list::iterator modMat; for(modMat = this->materialList.begin(); modMat != this->materialList.end(); modMat++) { if (!(*modMat)->external) delete (*modMat)->material; delete (*modMat); } } /** * @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"); ModelGroup* tmpGroup = this->firstGroup; while (tmpGroup != NULL) { PRINTF(5)("Drawing model %s\n", tmpGroup->name.c_str()); glCallList (tmpGroup->listNumber); tmpGroup = tmpGroup->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 StaticModelData::draw (int groupNumber) const { if (unlikely(groupNumber >= this->groupCount)) { PRINTF(2)("You requested model number %i, but this File only contains of %i Models.\n", groupNumber-1, this->groupCount); return; } PRINTF(4)("drawing the requested 3D-Models if found.\n"); ModelGroup* tmpGroup = this->firstGroup; int counter = 0; while (tmpGroup != NULL) { if (counter == groupNumber) { PRINTF(4)("Drawing model number %i named %s\n", counter, tmpGroup->name.c_str()); glCallList (tmpGroup->listNumber); return; } ++counter; tmpGroup = tmpGroup->next; } PRINTF(2)("Model number %i in %s not Found.\n", groupNumber, this->getCName()); 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 StaticModelData::draw (const std::string& groupName) const { PRINTF(4)("drawing the requested 3D-Models if found.\n"); ModelGroup* tmpGroup = this->firstGroup; while (tmpGroup != NULL) { if (tmpGroup->name == groupName) { PRINTF(4)("Drawing model %s\n", tmpGroup->name.c_str()); glCallList (tmpGroup->listNumber); return; } tmpGroup = tmpGroup->next; } PRINTF(2)("Model Named %s in %s not Found.\n", groupName.c_str(), this->getCName()); return; } ////////// // 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"); this->firstGroup->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(Material* material) { if (material == NULL) return NULL; ModelMaterial* modMat = new ModelMaterial; modMat->external = true; modMat->material = material; this->materialList.push_back(modMat); return modMat->material; } /** * @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) { ModelMaterial* modMat = new ModelMaterial; modMat->external = false; modMat->material = new Material(materialName); // adding material to the List of materials this->materialList.push_back(modMat); return modMat->material; } /** * @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 modMat; for (modMat = this->materialList.begin(); modMat != this->materialList.end(); modMat++) if (materialName == (*modMat)->material->getName()) return (*modMat)->material; 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(5)("Read Group: %s.\n", groupString.c_str()); if (this->groupCount != 0 && this->currentGroup->faceCount > 0) { // finalizeGroup(currentGroup); this->currentGroup = this->currentGroup->next = new ModelGroup; } // setting the group name if not default. if (groupString == "default") { this->currentGroup->name = groupString; } ++this->groupCount; 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(); if (this->currentGroup->faceCount >0) this->currentGroup->currentFace = this->currentGroup->currentFace->next = new ModelFace; ModelFaceElement* tmpElem = this->currentGroup->currentFace->firstElem = new ModelFaceElement; tmpElem->next = NULL; while(strcmp (faceString, "\0")) { if (this->currentGroup->currentFace->vertexCount>0) tmpElem = tmpElem->next = new ModelFaceElement; 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; if (texture) tmpElem->texCoordNumber = atoi(texture)-1; if (normal) tmpElem->normalNumber = atoi(normal)-1; faceString += tmpLen; if (strcmp (faceString, "\0")) faceString++; this->currentGroup->currentFace->vertexCount++; } this->currentGroup->faceCount += this->currentGroup->currentFace->vertexCount -2; this->faceCount += this->currentGroup->currentFace->vertexCount -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) { if (this->currentGroup->faceCount > 0) this->currentGroup->currentFace = this->currentGroup->currentFace->next = new ModelFace; ModelFaceElement* tmpElem = this->currentGroup->currentFace->firstElem = new ModelFaceElement; for (int i = 0; i < faceElemCount; i++) { if (this->currentGroup->currentFace->vertexCount > 0) tmpElem = tmpElem->next = new ModelFaceElement; tmpElem->vertexNumber = va_arg (args, int); if (type & TEXCOORD) tmpElem->texCoordNumber = va_arg (args, int); if (type & NORMAL) tmpElem->normalNumber = va_arg(args, int); this->currentGroup->currentFace->vertexCount++; } this->currentGroup->faceCount += this->currentGroup->currentFace->vertexCount - 2; this->faceCount += this->currentGroup->currentFace->vertexCount -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) { if (this->currentGroup->faceCount > 0) this->currentGroup->currentFace = this->currentGroup->currentFace->next = new ModelFace; this->currentGroup->currentFace->material = this->findMaterialByName(matString); if (this->currentGroup->faceCount == 0) this->currentGroup->faceCount++; 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) { if (this->currentGroup->faceCount > 0) this->currentGroup->currentFace = this->currentGroup->currentFace->next = new ModelFace; this->currentGroup->currentFace->material = mtl; if (this->currentGroup->faceCount == 0) this->currentGroup->faceCount++; 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; ifirstFace; while (tmpFace != NULL) { if (tmpFace->firstElem != NULL) { ModelFaceElement* firstElem = tmpFace->firstElem; ModelFaceElement* prevElem; ModelFaceElement* curElem = firstElem; ModelFaceElement* nextElem; ModelFaceElement* 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 != NULL) { prevElem = curElem; curElem = curElem->next; } lastElem = prevElem; curElem = firstElem; for (unsigned int j = 0; j < tmpFace->vertexCount; j++) { if (!(nextElem = curElem->next)) 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 = curElem->next; } } tmpFace = tmpFace->next; } tmpGroup = tmpGroup->next; } 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(); this->currentGroup = this->firstGroup; while (this->currentGroup != NULL) { // creating a glList for the Group if ((this->currentGroup->listNumber = glGenLists(1)) == 0) { PRINTF(2)("glList could not be created for this Model\n"); return false; } glNewList (this->currentGroup->listNumber, GL_COMPILE); // Putting Faces to GL ModelFace* tmpFace = this->currentGroup->firstFace; while (tmpFace != NULL) { if (tmpFace->vertexCount == 0 && tmpFace->material != NULL) { if (this->currentGroup->faceMode != -1) glEnd(); this->currentGroup->faceMode = 0; if (tmpFace->material != NULL) { tmpFace->material->select(); PRINTF(5)("using material %s for coming Faces.\n", tmpFace->material->getCName()); } } else if (tmpFace->vertexCount == 3) { if (this->currentGroup->faceMode != 3) { if (this->currentGroup->faceMode != -1) glEnd(); glBegin(GL_TRIANGLES); } this->currentGroup->faceMode = 3; PRINTF(5)("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(5)("found quad.\n"); } else if (tmpFace->vertexCount > 4) { if (this->currentGroup->faceMode != -1) glEnd(); glBegin(GL_POLYGON); PRINTF(5)("Polygon with %i faces found.", tmpFace->vertexCount); this->currentGroup->faceMode = tmpFace->vertexCount; } ModelFaceElement* 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; } 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 ModelFaceElement* tmpElem; //!< the temporary faceelement reference ModelFace* tmpFace; //!< the temporary face referece unsigned int numTriangles = 0; bool warned = false; /* count the number of triangles */ /* now iterate through all groups and build up the triangle list */ this->currentGroup = this->firstGroup; while( this->currentGroup != NULL) { tmpFace = this->currentGroup->firstFace; while( tmpFace != NULL) { /* if its a triangle just add it to the list */ if( tmpFace->vertexCount == 3) { ++numTriangles; } /* if the polygon is a quad */ else if( tmpFace->vertexCount == 4) { numTriangles += 2; } else if( tmpFace->vertexCount > 4) { if (!warned) { PRINTF(2)("This model (%s) got over 4 vertices per face <=> conflicts in the CD engine!\n", this->getCName()); warned = true; } } tmpFace = tmpFace->next; } this->currentGroup = this->currentGroup->next; } 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 */ this->currentGroup = this->firstGroup; while( this->currentGroup != NULL) { tmpFace = this->currentGroup->firstFace; while( tmpFace != NULL) { tmpElem = tmpFace->firstElem; /* if its a triangle just add it to the list */ if( tmpFace->vertexCount == 3) { for( int j = 0; j < 3; ++j) { this->triangles[index].indexToVertices[j] = (unsigned int)tmpElem->vertexNumber * 3 ; this->triangles[index].indexToNormals[j] = (unsigned int)tmpElem->normalNumber * 3 ; this->triangles[index].indexToTexCoor[j] = (unsigned int)tmpElem->texCoordNumber * 3 ; tmpElem = tmpElem->next; } ++index; } /* if the polygon is a quad */ else if( tmpFace->vertexCount == 4) { this->triangles[index].indexToVertices[0] = (unsigned int)tmpElem->vertexNumber * 3; this->triangles[index].indexToNormals[0] = (unsigned int)tmpElem->normalNumber * 3; this->triangles[index].indexToTexCoor[0] = (unsigned int)tmpElem->texCoordNumber * 3; this->triangles[index + 1].indexToVertices[0] = (unsigned int)tmpElem->vertexNumber * 3; this->triangles[index + 1].indexToNormals[0] = (unsigned int)tmpElem->normalNumber * 3; this->triangles[index + 1].indexToTexCoor[0] = (unsigned int)tmpElem->texCoordNumber * 3; tmpElem = tmpElem->next; this->triangles[index].indexToVertices[1] = (unsigned int)tmpElem->vertexNumber * 3; this->triangles[index].indexToNormals[1] = (unsigned int)tmpElem->normalNumber * 3; this->triangles[index].indexToTexCoor[1] = (unsigned int)tmpElem->texCoordNumber * 3; tmpElem = tmpElem->next; this->triangles[index].indexToVertices[2] = (unsigned int)tmpElem->vertexNumber * 3; this->triangles[index].indexToNormals[2] = (unsigned int)tmpElem->normalNumber * 3; this->triangles[index].indexToTexCoor[2] = (unsigned int)tmpElem->texCoordNumber * 3; this->triangles[index + 1].indexToVertices[2] = (unsigned int)tmpElem->vertexNumber * 3; this->triangles[index + 1].indexToNormals[2] = (unsigned int)tmpElem->normalNumber * 3; this->triangles[index + 1].indexToTexCoor[2] = (unsigned int)tmpElem->texCoordNumber * 3; tmpElem = tmpElem->next; this->triangles[index + 1].indexToVertices[1] = (unsigned int)tmpElem->vertexNumber * 3; this->triangles[index + 1].indexToNormals[1] = (unsigned int)tmpElem->normalNumber * 3; this->triangles[index + 1].indexToTexCoor[1] = (unsigned int)tmpElem->texCoordNumber * 3; index += 2; } tmpFace = tmpFace->next; } this->currentGroup = this->currentGroup->next; } 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 (ModelFaceElement* 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; }