CC_FILE_ERROR ObjFilter::saveToFile(ccHObject* entity, QString filename, SaveParameters& parameters) { if (!entity) return CC_FERR_BAD_ARGUMENT; if (!entity->isKindOf(CC_TYPES::MESH)) { ccLog::Warning("[OBJ] This filter can only save one mesh (optionally with sub-meshes) at a time!"); return CC_FERR_BAD_ENTITY_TYPE; } //mesh ccGenericMesh* mesh = ccHObjectCaster::ToGenericMesh(entity); if (!mesh || mesh->size() == 0) { ccLog::Warning("[OBJ] Input mesh is empty!"); return CC_FERR_NO_SAVE; } //vertices ccGenericPointCloud* vertices = mesh->getAssociatedCloud(); if (!vertices || vertices->size() == 0) { ccLog::Warning("[OBJ] Input mesh has no vertices?!"); return CC_FERR_NO_SAVE; } unsigned nbPoints = vertices->size(); //try to open file for saving QFile file(filename); if (!file.open(QFile::Text | QFile::WriteOnly)) return CC_FERR_WRITING; //progress ccProgressDialog pdlg(true); unsigned numberOfTriangles = mesh->size(); CCLib::NormalizedProgress nprogress(&pdlg,numberOfTriangles); pdlg.setMethodTitle(qPrintable(QString("Saving mesh [%1]").arg(mesh->getName()))); pdlg.setInfo(qPrintable(QString("Triangles: %1").arg(numberOfTriangles))); pdlg.start(); QTextStream stream(&file); stream.setRealNumberPrecision(sizeof(PointCoordinateType) == 4 ? 8 : 12); stream << "#OBJ Generated by CloudCompare (TELECOM PARISTECH/EDF R&D)" << endl; if (file.error() != QFile::NoError) return CC_FERR_WRITING; for (unsigned i=0; i<nbPoints; ++i) { const CCVector3* P = vertices->getPoint(i); CCVector3d Pglobal = vertices->toGlobal3d<PointCoordinateType>(*P); stream << "v " << Pglobal.x << " " << Pglobal.y << " " << Pglobal.z << endl; if (file.error() != QFile::NoError) return CC_FERR_WRITING; } //normals bool withTriNormals = mesh->hasTriNormals(); bool withVertNormals = vertices->hasNormals(); bool withNormals = withTriNormals || withVertNormals; if (withNormals) { //per-triangle normals if (withTriNormals) { NormsIndexesTableType* normsTable = mesh->getTriNormsTable(); if (normsTable) { for (unsigned i=0; i<normsTable->currentSize(); ++i) { const CCVector3& normalVec = ccNormalVectors::GetNormal(normsTable->getValue(i)); stream << "vn " << normalVec.x << " " << normalVec.y << " " << normalVec.z << endl; if (file.error() != QFile::NoError) return CC_FERR_WRITING; } } else { assert(false); withTriNormals = false; } } //per-vertices normals else //if (withVertNormals) { for (unsigned i=0; i<nbPoints; ++i) { const CCVector3& normalVec = vertices->getPointNormal(i); stream << "vn " << normalVec.x << " " << normalVec.y << " " << normalVec.z << endl; if (file.error() != QFile::NoError) return CC_FERR_WRITING; } } } //materials const ccMaterialSet* materials = mesh->getMaterialSet(); bool withMaterials = (materials && mesh->hasMaterials()); if (withMaterials) { //save mtl file QStringList errors; QString baseName = QFileInfo(filename).baseName(); if (materials->saveAsMTL(QFileInfo(filename).absolutePath(),baseName,errors)) { stream << "mtllib " << baseName << ".mtl" << endl; if (file.error() != QFile::NoError) return CC_FERR_WRITING; } else { materials = 0; withMaterials = false; } //display potential 'errors' for (int i=0; i<errors.size(); ++i) { ccLog::Warning(QString("[OBJ][Material file writer] ")+errors[i]); } } //save texture coordinates bool withTexCoordinates = withMaterials && mesh->hasPerTriangleTexCoordIndexes(); if (withTexCoordinates) { TextureCoordsContainer* texCoords = mesh->getTexCoordinatesTable(); if (texCoords) { for (unsigned i=0; i<texCoords->currentSize(); ++i) { const float* tc = texCoords->getValue(i); stream << "vt " << tc[0] << " " << tc[1] << endl; if (file.error() != QFile::NoError) return CC_FERR_WRITING; } } else { withTexCoordinates = false; } } ccHObject::Container subMeshes; //look for sub-meshes mesh->filterChildren(subMeshes,false,CC_TYPES::SUB_MESH); //check that the number of facets is the same as the full mesh! { unsigned faceCount = 0; for (ccHObject::Container::const_iterator it = subMeshes.begin(); it != subMeshes.end(); ++it) faceCount += static_cast<ccSubMesh*>(*it)->size(); //if there's no face (i.e. no sub-mesh) or less face than the total mesh, we save the full mesh! if (faceCount < mesh->size()) { subMeshes.clear(); subMeshes.push_back(mesh); } } //mesh or sub-meshes unsigned indexShift = 0; for (ccHObject::Container::const_iterator it = subMeshes.begin(); it != subMeshes.end(); ++it) { ccGenericMesh* st = static_cast<ccGenericMesh*>(*it); stream << "g " << (st->getName().isNull() ? "mesh" : st->getName()) << endl; if (file.error() != QFile::NoError) return CC_FERR_WRITING; unsigned triNum = st->size(); st->placeIteratorAtBegining(); int lastMtlIndex = -1; int t1 = -1, t2 = -1, t3 = -1; for (unsigned i=0; i<triNum; ++i) { if (withMaterials) { int mtlIndex = mesh->getTriangleMtlIndex(indexShift+i); if (mtlIndex != lastMtlIndex) { if (mtlIndex >= 0 && mtlIndex < static_cast<int>(materials->size())) { ccMaterial::CShared mat = materials->at(mtlIndex); stream << "usemtl " << mat->getName() << endl; } else { stream << "usemtl " << endl; } if (file.error() != QFile::NoError) return CC_FERR_WRITING; lastMtlIndex = mtlIndex; } if (withTexCoordinates) { mesh->getTriangleTexCoordinatesIndexes(indexShift+i,t1,t2,t3); if (t1 >= 0) ++t1; if (t2 >= 0) ++t2; if (t3 >= 0) ++t3; } } const CCLib::VerticesIndexes* tsi = st->getNextTriangleVertIndexes(); //for per-triangle normals unsigned i1 = tsi->i1 + 1; unsigned i2 = tsi->i2 + 1; unsigned i3 = tsi->i3 + 1; stream << "f"; if (withNormals) { int n1 = static_cast<int>(i1); int n2 = static_cast<int>(i2); int n3 = static_cast<int>(i3); if (withTriNormals) { st->getTriangleNormalIndexes(i,n1,n2,n3); if (n1 >= 0) ++n1; if (n2 >= 0) ++n2; if (n3 >= 0) ++n3; } if (withTexCoordinates) { stream << " " << i1 << "/" << t1 << "/" << n1; stream << " " << i2 << "/" << t2 << "/" << n2; stream << " " << i3 << "/" << t3 << "/" << n3; } else { stream << " " << i1 << "//" << n1; stream << " " << i2 << "//" << n2; stream << " " << i3 << "//" << n3; } } else { if (withTexCoordinates) { stream << " " << i1 << "/" << t1; stream << " " << i2 << "/" << t2; stream << " " << i3 << "/" << t3; } else { stream << " " << i1; stream << " " << i2; stream << " " << i3; } } stream << endl; if (file.error() != QFile::NoError) return CC_FERR_WRITING; if (!nprogress.oneStep()) //cancel requested return CC_FERR_CANCELED_BY_USER; } stream << "#" << triNum << " faces" << endl; if (file.error() != QFile::NoError) return CC_FERR_WRITING; indexShift += triNum; } return CC_FERR_NO_ERROR; }
//converts a FBX mesh to a CC mesh static ccMesh* FromFbxMesh(FbxMesh* fbxMesh, bool alwaysDisplayLoadDialog/*=true*/, bool* coordinatesShiftEnabled/*=0*/, CCVector3d* coordinatesShift/*=0*/) { if (!fbxMesh) return 0; int polyCount = fbxMesh->GetPolygonCount(); //fbxMesh->GetLayer( unsigned triCount = 0; unsigned polyVertCount = 0; //different from vertCount (vertices can be counted multiple times here!) //as we can't load all polygons (yet ;) we already look if we can load any! { unsigned skipped = 0; for (int i=0; i<polyCount; ++i) { int pSize = fbxMesh->GetPolygonSize(i); if (pSize == 3) { ++triCount; polyVertCount += 3; } else if (pSize == 4) { triCount += 2; polyVertCount += 4; } else { ++skipped; } } if (triCount == 0) { ccLog::Warning(QString("[FBX] No triangle or quad found in mesh '%1'! (polygons with more than 4 vertices are not supported for the moment)").arg(fbxMesh->GetName())); return 0; } else if (skipped != 0) { ccLog::Warning(QString("[FBX] Some polygons in mesh '%1' were ignored (%2): polygons with more than 4 vertices are not supported for the moment)").arg(fbxMesh->GetName()).arg(skipped)); return 0; } } int vertCount = fbxMesh->GetControlPointsCount(); if (vertCount <= 0) { ccLog::Warning(QString("[FBX] Mesh '%1' has no vetex or no polygon?!").arg(fbxMesh->GetName())); return 0; } ccPointCloud* vertices = new ccPointCloud("vertices"); ccMesh* mesh = new ccMesh(vertices); mesh->setName(fbxMesh->GetName()); mesh->addChild(vertices); vertices->setEnabled(false); if (!mesh->reserve(static_cast<unsigned>(triCount)) || !vertices->reserve(vertCount)) { ccLog::Warning(QString("[FBX] Not enough memory to load mesh '%1'!").arg(fbxMesh->GetName())); delete mesh; return 0; } //colors { for (int l=0; l<fbxMesh->GetElementVertexColorCount(); l++) { FbxGeometryElementVertexColor* vertColor = fbxMesh->GetElementVertexColor(l); //CC can only handle per-vertex colors if (vertColor->GetMappingMode() == FbxGeometryElement::eByControlPoint) { if (vertColor->GetReferenceMode() == FbxGeometryElement::eDirect || vertColor->GetReferenceMode() == FbxGeometryElement::eIndexToDirect) { if (vertices->reserveTheRGBTable()) { switch (vertColor->GetReferenceMode()) { case FbxGeometryElement::eDirect: { for (int i=0; i<vertCount; ++i) { FbxColor c = vertColor->GetDirectArray().GetAt(i); vertices->addRGBColor( static_cast<colorType>(c.mRed * MAX_COLOR_COMP), static_cast<colorType>(c.mGreen * MAX_COLOR_COMP), static_cast<colorType>(c.mBlue * MAX_COLOR_COMP) ); } } break; case FbxGeometryElement::eIndexToDirect: { for (int i=0; i<vertCount; ++i) { int id = vertColor->GetIndexArray().GetAt(i); FbxColor c = vertColor->GetDirectArray().GetAt(id); vertices->addRGBColor( static_cast<colorType>(c.mRed * MAX_COLOR_COMP), static_cast<colorType>(c.mGreen * MAX_COLOR_COMP), static_cast<colorType>(c.mBlue * MAX_COLOR_COMP) ); } } break; default: assert(false); break; } vertices->showColors(true); mesh->showColors(true); break; //no need to look for other color fields (we won't be able to handle them! } else { ccLog::Warning(QString("[FBX] Not enough memory to load mesh '%1' colors!").arg(fbxMesh->GetName())); } } else { ccLog::Warning(QString("[FBX] Color field #%i of mesh '%1' will be ignored (unhandled type)").arg(l).arg(fbxMesh->GetName())); } } else { ccLog::Warning(QString("[FBX] Color field #%i of mesh '%1' will be ignored (unhandled type)").arg(l).arg(fbxMesh->GetName())); } } } //normals can be per vertices or per-triangle int perPointNormals = -1; int perVertexNormals = -1; int perPolygonNormals = -1; { for (int j=0; j<fbxMesh->GetElementNormalCount(); j++) { FbxGeometryElementNormal* leNormals = fbxMesh->GetElementNormal(j); switch(leNormals->GetMappingMode()) { case FbxGeometryElement::eByControlPoint: perPointNormals = j; break; case FbxGeometryElement::eByPolygonVertex: perVertexNormals = j; break; case FbxGeometryElement::eByPolygon: perPolygonNormals = j; break; default: //not handled break; } } } //per-point normals if (perPointNormals >= 0) { FbxGeometryElementNormal* leNormals = fbxMesh->GetElementNormal(perPointNormals); FbxLayerElement::EReferenceMode refMode = leNormals->GetReferenceMode(); const FbxLayerElementArrayTemplate<FbxVector4>& normals = leNormals->GetDirectArray(); assert(normals.GetCount() == vertCount); if (normals.GetCount() != vertCount) { ccLog::Warning(QString("[FBX] Wrong number of normals on mesh '%1'!").arg(fbxMesh->GetName())); perPointNormals = -1; } else if (!vertices->reserveTheNormsTable()) { ccLog::Warning(QString("[FBX] Not enough memory to load mesh '%1' normals!").arg(fbxMesh->GetName())); perPointNormals = -1; } else { //import normals for (int i=0; i<vertCount; ++i) { int id = refMode != FbxGeometryElement::eDirect ? leNormals->GetIndexArray().GetAt(i) : i; FbxVector4 N = normals.GetAt(id); //convert to CC-structure CCVector3 Npc( static_cast<PointCoordinateType>(N.Buffer()[0]), static_cast<PointCoordinateType>(N.Buffer()[1]), static_cast<PointCoordinateType>(N.Buffer()[2]) ); vertices->addNorm(Npc.u); } vertices->showNormals(true); mesh->showNormals(true); //no need to import the other normals (if any) perVertexNormals = -1; perPolygonNormals = -1; } } //per-triangle normals NormsIndexesTableType* normsTable = 0; if (perVertexNormals >= 0 || perPolygonNormals >= 0) { normsTable = new NormsIndexesTableType(); if (!normsTable->reserve(polyVertCount) || !mesh->reservePerTriangleNormalIndexes()) { ccLog::Warning(QString("[FBX] Not enough memory to load mesh '%1' normals!").arg(fbxMesh->GetName())); normsTable->release(); normsTable = 0; } else { mesh->setTriNormsTable(normsTable); mesh->addChild(normsTable); vertices->showNormals(true); mesh->showNormals(true); } } //import textures UV int perVertexUV = -1; bool hasTexUV = false; { for (int l=0; l<fbxMesh->GetElementUVCount(); ++l) { FbxGeometryElementUV* leUV = fbxMesh->GetElementUV(l); //per-point UV coordinates if (leUV->GetMappingMode() == FbxGeometryElement::eByControlPoint) { TextureCoordsContainer* vertTexUVTable = new TextureCoordsContainer(); if (!vertTexUVTable->reserve(vertCount) || !mesh->reservePerTriangleTexCoordIndexes()) { vertTexUVTable->release(); ccLog::Warning(QString("[FBX] Not enough memory to load mesh '%1' UV coordinates!").arg(fbxMesh->GetName())); } else { FbxLayerElement::EReferenceMode refMode = leUV->GetReferenceMode(); for (int i=0; i<vertCount; ++i) { int id = refMode != FbxGeometryElement::eDirect ? leUV->GetIndexArray().GetAt(i) : i; FbxVector2 uv = leUV->GetDirectArray().GetAt(id); //convert to CC-structure float uvf[2] = {static_cast<float>(uv.Buffer()[0]), static_cast<float>(uv.Buffer()[1])}; vertTexUVTable->addElement(uvf); } mesh->addChild(vertTexUVTable); hasTexUV = true; } perVertexUV = -1; break; //no need to look to the other UV fields (can't handle them!) } else if (leUV->GetMappingMode() == FbxGeometryElement::eByPolygonVertex) { //per-vertex UV coordinates perVertexUV = l; } } } //per-vertex UV coordinates TextureCoordsContainer* texUVTable = 0; if (perVertexUV >= 0) { texUVTable = new TextureCoordsContainer(); if (!texUVTable->reserve(polyVertCount) || !mesh->reservePerTriangleTexCoordIndexes()) { texUVTable->release(); ccLog::Warning(QString("[FBX] Not enough memory to load mesh '%1' UV coordinates!").arg(fbxMesh->GetName())); } else { mesh->addChild(texUVTable); hasTexUV = true; } } //import polygons { for (int i=0; i<polyCount; ++i) { int pSize = fbxMesh->GetPolygonSize(i); if (pSize > 4) { //not handled for the moment continue; } //we split quads into two triangles //vertex indices int i1 = fbxMesh->GetPolygonVertex(i, 0); int i2 = fbxMesh->GetPolygonVertex(i, 1); int i3 = fbxMesh->GetPolygonVertex(i, 2); mesh->addTriangle(i1,i2,i3); int i4 = -1; if (pSize == 4) { i4 = fbxMesh->GetPolygonVertex(i, 3); mesh->addTriangle(i1,i3,i4); } if (hasTexUV) { if (texUVTable) { assert(perVertexUV >= 0); int uvIndex = static_cast<int>(texUVTable->currentSize()); for (int j=0; j<pSize; ++j) { int lTextureUVIndex = fbxMesh->GetTextureUVIndex(i, j); FbxGeometryElementUV* leUV = fbxMesh->GetElementUV(perVertexUV); FbxVector2 uv = leUV->GetDirectArray().GetAt(lTextureUVIndex); //convert to CC-structure float uvf[2] = {static_cast<float>(uv.Buffer()[0]), static_cast<float>(uv.Buffer()[1])}; texUVTable->addElement(uvf); } mesh->addTriangleTexCoordIndexes(uvIndex,uvIndex+1,uvIndex+2); if (pSize == 4) mesh->addTriangleTexCoordIndexes(uvIndex,uvIndex+2,uvIndex+3); } else { mesh->addTriangleTexCoordIndexes(i1,i2,i3); if (pSize == 4) mesh->addTriangleTexCoordIndexes(i1,i3,i4); } } //per-triangle normals if (normsTable) { int nIndex = static_cast<int>(normsTable->currentSize()); for (int j=0; j<pSize; ++j) { FbxVector4 N; fbxMesh->GetPolygonVertexNormal(i, j, N); CCVector3 Npc( static_cast<PointCoordinateType>(N.Buffer()[0]), static_cast<PointCoordinateType>(N.Buffer()[1]), static_cast<PointCoordinateType>(N.Buffer()[2]) ); normsTable->addElement(ccNormalVectors::GetNormIndex(Npc.u)); } mesh->addTriangleNormalIndexes(nIndex,nIndex+1,nIndex+2); if (pSize == 4) mesh->addTriangleNormalIndexes(nIndex,nIndex+2,nIndex+3); } } if (mesh->size() == 0) { ccLog::Warning(QString("[FBX] No triangle found in mesh '%1'! (only triangles are supported for the moment)").arg(fbxMesh->GetName())); delete mesh; return 0; } } //import vertices { const FbxVector4* fbxVertices = fbxMesh->GetControlPoints(); assert(vertices && fbxVertices); CCVector3d Pshift(0,0,0); for (int i=0; i<vertCount; ++i, ++fbxVertices) { const double* P = fbxVertices->Buffer(); assert(P[3] == 0); //coordinate shift management if (i == 0) { bool shiftAlreadyEnabled = (coordinatesShiftEnabled && *coordinatesShiftEnabled && coordinatesShift); if (shiftAlreadyEnabled) Pshift = *coordinatesShift; bool applyAll = false; if ( sizeof(PointCoordinateType) < 8 && ccCoordinatesShiftManager::Handle(P,0,alwaysDisplayLoadDialog,shiftAlreadyEnabled,Pshift,0,applyAll)) { vertices->setGlobalShift(Pshift); ccLog::Warning("[FBX] Mesh has been recentered! Translation: (%.2f,%.2f,%.2f)",Pshift.x,Pshift.y,Pshift.z); //we save coordinates shift information if (applyAll && coordinatesShiftEnabled && coordinatesShift) { *coordinatesShiftEnabled = true; *coordinatesShift = Pshift; } } } CCVector3 PV( static_cast<PointCoordinateType>(P[0] + Pshift.x), static_cast<PointCoordinateType>(P[1] + Pshift.y), static_cast<PointCoordinateType>(P[2] + Pshift.z) ); vertices->addPoint(PV); } } //import textures { //TODO } return mesh; }
CC_FILE_ERROR ObjFilter::loadFile(QString filename, ccHObject& container, LoadParameters& parameters) { ccLog::Print(QString("[OBJ] ") + filename); //open file QFile file(filename); if (!file.open(QFile::ReadOnly)) return CC_FERR_READING; QTextStream stream(&file); //current vertex shift CCVector3d Pshift(0,0,0); //vertices ccPointCloud* vertices = new ccPointCloud("vertices"); int pointsRead = 0; //facets unsigned int facesRead = 0; unsigned int totalFacesRead = 0; int maxVertexIndex = -1; //base mesh ccMesh* baseMesh = new ccMesh(vertices); baseMesh->setName(QFileInfo(filename).baseName()); //we need some space already reserved! if (!baseMesh->reserve(128)) { ccLog::Error("Not engouh memory!"); return CC_FERR_NOT_ENOUGH_MEMORY; } //groups (starting index + name) std::vector<std::pair<unsigned,QString> > groups; //materials ccMaterialSet* materials = 0; bool hasMaterial = false; int currentMaterial = -1; bool currentMaterialDefined = false; bool materialsLoadFailed = true; //texture coordinates TextureCoordsContainer* texCoords = 0; bool hasTexCoords = false; int texCoordsRead = 0; int maxTexCoordIndex = -1; //normals NormsIndexesTableType* normals = 0; int normsRead = 0; bool normalsPerFacet = false; int maxTriNormIndex = -1; //progress dialog ccProgressDialog pDlg(true); pDlg.setMethodTitle("OBJ file"); pDlg.setInfo("Loading in progress..."); pDlg.setRange(0,static_cast<int>(file.size())); pDlg.show(); QApplication::processEvents(); //common warnings that can appear multiple time (we avoid to send too many messages to the console!) enum OBJ_WARNINGS { INVALID_NORMALS = 0, INVALID_INDEX = 1, NOT_ENOUGH_MEMORY = 2, INVALID_LINE = 3, CANCELLED_BY_USER = 4, }; bool objWarnings[5] = { false, false, false, false, false }; bool error = false; try { unsigned lineCount = 0; unsigned polyCount = 0; QString currentLine = stream.readLine(); while (!currentLine.isNull()) { if ((++lineCount % 2048) == 0) { if (pDlg.wasCanceled()) { error = true; objWarnings[CANCELLED_BY_USER] = true; break; } pDlg.setValue(static_cast<int>(file.pos())); QApplication::processEvents(); } QStringList tokens = QString(currentLine).split(QRegExp("\\s+"),QString::SkipEmptyParts); //skip comments & empty lines if( tokens.empty() || tokens.front().startsWith('/',Qt::CaseInsensitive) || tokens.front().startsWith('#',Qt::CaseInsensitive) ) { currentLine = stream.readLine(); continue; } /*** new vertex ***/ if (tokens.front() == "v") { //reserve more memory if necessary if (vertices->size() == vertices->capacity()) { if (!vertices->reserve(vertices->capacity()+MAX_NUMBER_OF_ELEMENTS_PER_CHUNK)) { objWarnings[NOT_ENOUGH_MEMORY] = true; error = true; break; } } //malformed line? if (tokens.size() < 4) { objWarnings[INVALID_LINE] = true; error = true; break; } CCVector3d Pd( tokens[1].toDouble(), tokens[2].toDouble(), tokens[3].toDouble() ); //first point: check for 'big' coordinates if (pointsRead == 0) { if (HandleGlobalShift(Pd,Pshift,parameters)) { vertices->setGlobalShift(Pshift); ccLog::Warning("[OBJ] Cloud has been recentered! Translation: (%.2f,%.2f,%.2f)",Pshift.x,Pshift.y,Pshift.z); } } //shifted point CCVector3 P = CCVector3::fromArray((Pd + Pshift).u); vertices->addPoint(P); ++pointsRead; } /*** new vertex texture coordinates ***/ else if (tokens.front() == "vt") { //create and reserve memory for tex. coords container if necessary if (!texCoords) { texCoords = new TextureCoordsContainer(); texCoords->link(); } if (texCoords->currentSize() == texCoords->capacity()) { if (!texCoords->reserve(texCoords->capacity() + MAX_NUMBER_OF_ELEMENTS_PER_CHUNK)) { objWarnings[NOT_ENOUGH_MEMORY] = true; error = true; break; } } //malformed line? if (tokens.size() < 2) { objWarnings[INVALID_LINE] = true; error = true; break; } float T[2] = { T[0] = tokens[1].toFloat(), 0 }; if (tokens.size() > 2) //OBJ specification allows for only one value!!! { T[1] = tokens[2].toFloat(); } texCoords->addElement(T); ++texCoordsRead; } /*** new vertex normal ***/ else if (tokens.front() == "vn") //--> in fact it can also be a facet normal!!! { //create and reserve memory for normals container if necessary if (!normals) { normals = new NormsIndexesTableType; normals->link(); } if (normals->currentSize() == normals->capacity()) { if (!normals->reserve(normals->capacity() + MAX_NUMBER_OF_ELEMENTS_PER_CHUNK)) { objWarnings[NOT_ENOUGH_MEMORY] = true; error = true; break; } } //malformed line? if (tokens.size() < 4) { objWarnings[INVALID_LINE] = true; error = true; break; } CCVector3 N(static_cast<PointCoordinateType>(tokens[1].toDouble()), static_cast<PointCoordinateType>(tokens[2].toDouble()), static_cast<PointCoordinateType>(tokens[3].toDouble())); if (fabs(N.norm2() - 1.0) > 0.005) { objWarnings[INVALID_NORMALS] = true; N.normalize(); } CompressedNormType nIndex = ccNormalVectors::GetNormIndex(N.u); normals->addElement(nIndex); //we don't know yet if it's per-vertex or per-triangle normal... ++normsRead; } /*** new group ***/ else if (tokens.front() == "g" || tokens.front() == "o") { //update new group index facesRead = 0; //get the group name QString groupName = (tokens.size() > 1 && !tokens[1].isEmpty() ? tokens[1] : "default"); for (int i=2; i<tokens.size(); ++i) //multiple parts? groupName.append(QString(" ")+tokens[i]); //push previous group descriptor (if none was pushed) if (groups.empty() && totalFacesRead > 0) groups.push_back(std::pair<unsigned,QString>(0,"default")); //push new group descriptor if (!groups.empty() && groups.back().first == totalFacesRead) groups.back().second = groupName; //simply replace the group name if the previous group was empty! else groups.push_back(std::pair<unsigned,QString>(totalFacesRead,groupName)); polyCount = 0; //restart polyline count at 0! } /*** new face ***/ else if (tokens.front().startsWith('f')) { //malformed line? if (tokens.size() < 4) { objWarnings[INVALID_LINE] = true; currentLine = stream.readLine(); continue; //error = true; //break; } //read the face elements (singleton, pair or triplet) std::vector<facetElement> currentFace; { for (int i=1; i<tokens.size(); ++i) { QStringList vertexTokens = tokens[i].split('/'); if (vertexTokens.size() == 0 || vertexTokens[0].isEmpty()) { objWarnings[INVALID_LINE] = true; error = true; break; } else { //new vertex facetElement fe; //(0,0,0) by default fe.vIndex = vertexTokens[0].toInt(); if (vertexTokens.size() > 1 && !vertexTokens[1].isEmpty()) fe.tcIndex = vertexTokens[1].toInt(); if (vertexTokens.size() > 2 && !vertexTokens[2].isEmpty()) fe.nIndex = vertexTokens[2].toInt(); currentFace.push_back(fe); } } } if (error) break; if (currentFace.size() < 3) { ccLog::Warning("[OBJ] Malformed file: polygon on line %1 has less than 3 vertices!",lineCount); error = true; break; } //first vertex std::vector<facetElement>::iterator A = currentFace.begin(); //the very first vertex of the group tells us about the whole sequence if (facesRead == 0) { //we have a tex. coord index as second vertex element! if (!hasTexCoords && A->tcIndex != 0 && !materialsLoadFailed) { if (!baseMesh->reservePerTriangleTexCoordIndexes()) { objWarnings[NOT_ENOUGH_MEMORY] = true; error = true; break; } for (unsigned int i=0; i<totalFacesRead; ++i) baseMesh->addTriangleTexCoordIndexes(-1, -1, -1); hasTexCoords = true; } //we have a normal index as third vertex element! if (!normalsPerFacet && A->nIndex != 0) { //so the normals are 'per-facet' if (!baseMesh->reservePerTriangleNormalIndexes()) { objWarnings[NOT_ENOUGH_MEMORY] = true; error = true; break; } for (unsigned int i=0; i<totalFacesRead; ++i) baseMesh->addTriangleNormalIndexes(-1, -1, -1); normalsPerFacet = true; } } //we process all vertices accordingly for (std::vector<facetElement>::iterator it = currentFace.begin() ; it!=currentFace.end(); ++it) { facetElement& vertex = *it; //vertex index { if (!vertex.updatePointIndex(pointsRead)) { objWarnings[INVALID_INDEX] = true; error = true; break; } if (vertex.vIndex > maxVertexIndex) maxVertexIndex = vertex.vIndex; } //should we have a tex. coord index as second vertex element? if (hasTexCoords && currentMaterialDefined) { if (!vertex.updateTexCoordIndex(texCoordsRead)) { objWarnings[INVALID_INDEX] = true; error = true; break; } if (vertex.tcIndex > maxTexCoordIndex) maxTexCoordIndex = vertex.tcIndex; } //should we have a normal index as third vertex element? if (normalsPerFacet) { if (!vertex.updateNormalIndex(normsRead)) { objWarnings[INVALID_INDEX] = true; error = true; break; } if (vertex.nIndex > maxTriNormIndex) maxTriNormIndex = vertex.nIndex; } } //don't forget material (common for all vertices) if (currentMaterialDefined && !materialsLoadFailed) { if (!hasMaterial) { if (!baseMesh->reservePerTriangleMtlIndexes()) { objWarnings[NOT_ENOUGH_MEMORY] = true; error = true; break; } for (unsigned int i=0; i<totalFacesRead; ++i) baseMesh->addTriangleMtlIndex(-1); hasMaterial = true; } } if (error) break; //Now, let's tesselate the whole polygon //FIXME: yeah, we do very ulgy tesselation here! std::vector<facetElement>::const_iterator B = A+1; std::vector<facetElement>::const_iterator C = B+1; for ( ; C != currentFace.end(); ++B,++C) { //need more space? if (baseMesh->size() == baseMesh->capacity()) { if (!baseMesh->reserve(baseMesh->size()+128)) { objWarnings[NOT_ENOUGH_MEMORY] = true; error = true; break; } } //push new triangle baseMesh->addTriangle(A->vIndex, B->vIndex, C->vIndex); ++facesRead; ++totalFacesRead; if (hasMaterial) baseMesh->addTriangleMtlIndex(currentMaterial); if (hasTexCoords) baseMesh->addTriangleTexCoordIndexes(A->tcIndex, B->tcIndex, C->tcIndex); if (normalsPerFacet) baseMesh->addTriangleNormalIndexes(A->nIndex, B->nIndex, C->nIndex); } } /*** polyline ***/ else if (tokens.front().startsWith('l')) { //malformed line? if (tokens.size() < 3) { objWarnings[INVALID_LINE] = true; currentLine = stream.readLine(); continue; } //read the face elements (singleton, pair or triplet) ccPolyline* polyline = new ccPolyline(vertices); if (!polyline->reserve(static_cast<unsigned>(tokens.size()-1))) { //not enough memory objWarnings[NOT_ENOUGH_MEMORY] = true; delete polyline; polyline = 0; currentLine = stream.readLine(); continue; } for (int i=1; i<tokens.size(); ++i) { //get next polyline's vertex index QStringList vertexTokens = tokens[i].split('/'); if (vertexTokens.size() == 0 || vertexTokens[0].isEmpty()) { objWarnings[INVALID_LINE] = true; error = true; break; } else { int index = vertexTokens[0].toInt(); //we ignore normal index (if any!) if (!UpdatePointIndex(index,pointsRead)) { objWarnings[INVALID_INDEX] = true; error = true; break; } polyline->addPointIndex(index); } } if (error) { delete polyline; polyline = 0; break; } polyline->setVisible(true); QString name = groups.empty() ? QString("Line") : groups.back().second+QString(".line"); polyline->setName(QString("%1 %2").arg(name).arg(++polyCount)); vertices->addChild(polyline); } /*** material ***/ else if (tokens.front() == "usemtl") //see 'MTL file' below { if (materials) //otherwise we have failed to load MTL file!!! { QString mtlName = currentLine.mid(7).trimmed(); //DGM: in case there's space characters in the material name, we must read it again from the original line buffer //QString mtlName = (tokens.size() > 1 && !tokens[1].isEmpty() ? tokens[1] : ""); currentMaterial = (!mtlName.isEmpty() ? materials->findMaterialByName(mtlName) : -1); currentMaterialDefined = true; } } /*** material file (MTL) ***/ else if (tokens.front() == "mtllib") { //malformed line? if (tokens.size() < 2 || tokens[1].isEmpty()) { objWarnings[INVALID_LINE] = true; } else { //we build the whole MTL filename + path //DGM: in case there's space characters in the filename, we must read it again from the original line buffer //QString mtlFilename = tokens[1]; QString mtlFilename = currentLine.mid(7).trimmed(); ccLog::Print(QString("[OBJ] Material file: ")+mtlFilename); QString mtlPath = QFileInfo(filename).canonicalPath(); //we try to load it if (!materials) { materials = new ccMaterialSet("materials"); materials->link(); } size_t oldSize = materials->size(); QStringList errors; if (ccMaterialSet::ParseMTL(mtlPath,mtlFilename,*materials,errors)) { ccLog::Print("[OBJ] %i materials loaded",materials->size()-oldSize); materialsLoadFailed = false; } else { ccLog::Error(QString("[OBJ] Failed to load material file! (should be in '%1')").arg(mtlPath+'/'+QString(mtlFilename))); materialsLoadFailed = true; } if (!errors.empty()) { for (int i=0; i<errors.size(); ++i) ccLog::Warning(QString("[OBJ::Load::MTL parser] ")+errors[i]); } if (materials->empty()) { materials->release(); materials=0; materialsLoadFailed = true; } } } ///*** shading group ***/ //else if (tokens.front() == "s") //{ // //ignored! //} if (error) break; currentLine = stream.readLine(); } } catch (const std::bad_alloc&) { //not enough memory objWarnings[NOT_ENOUGH_MEMORY] = true; error = true; } file.close(); //1st check if (!error && pointsRead == 0) { //of course if there's no vertex, that's the end of the story ... ccLog::Warning("[OBJ] Malformed file: no vertex in file!"); error = true; } if (!error) { ccLog::Print("[OBJ] %i points, %u faces",pointsRead,totalFacesRead); if (texCoordsRead > 0 || normsRead > 0) ccLog::Print("[OBJ] %i tex. coords, %i normals",texCoordsRead,normsRead); //do some cleaning vertices->shrinkToFit(); if (normals) normals->shrinkToFit(); if (texCoords) texCoords->shrinkToFit(); if (baseMesh->size() == 0) { delete baseMesh; baseMesh = 0; } else { baseMesh->shrinkToFit(); } if ( maxVertexIndex >= pointsRead || maxTexCoordIndex >= texCoordsRead || maxTriNormIndex >= normsRead) { //hum, we've got a problem here ccLog::Warning("[OBJ] Malformed file: indexes go higher than the number of elements! (v=%i/tc=%i/n=%i)",maxVertexIndex,maxTexCoordIndex,maxTriNormIndex); if (maxVertexIndex >= pointsRead) { error = true; } else { objWarnings[INVALID_INDEX] = true; if (maxTexCoordIndex >= texCoordsRead) { texCoords->release(); texCoords = 0; materials->release(); materials = 0; } if (maxTriNormIndex >= normsRead) { normals->release(); normals = 0; } } } if (!error && baseMesh) { if (normals && normalsPerFacet) { baseMesh->setTriNormsTable(normals); baseMesh->showTriNorms(true); } if (materials) { baseMesh->setMaterialSet(materials); baseMesh->showMaterials(true); } if (texCoords) { if (materials) { baseMesh->setTexCoordinatesTable(texCoords); } else { ccLog::Warning("[OBJ] Texture coordinates were defined but no material could be loaded!"); } } //normals: if the obj file doesn't provide any, should we compute them? if (!normals) { //DGM: normals can be per-vertex or per-triangle so it's better to let the user do it himself later //Moreover it's not always good idea if the user doesn't want normals (especially in ccViewer!) //if (!materials && !baseMesh->hasColors()) //yes if no material is available! //{ // ccLog::Print("[OBJ] Mesh has no normal! We will compute them automatically"); // baseMesh->computeNormals(); // baseMesh->showNormals(true); //} //else { ccLog::Warning("[OBJ] Mesh has no normal! You can manually compute them (select it then call \"Edit > Normals > Compute\")"); } } //create sub-meshes if necessary ccLog::Print("[OBJ] 1 mesh loaded - %i group(s)", groups.size()); if (groups.size() > 1) { for (size_t i=0; i<groups.size(); ++i) { const QString& groupName = groups[i].second; unsigned startIndex = groups[i].first; unsigned endIndex = (i+1 == groups.size() ? baseMesh->size() : groups[i+1].first); if (startIndex == endIndex) { continue; } ccSubMesh* subTri = new ccSubMesh(baseMesh); if (subTri->reserve(endIndex-startIndex)) { subTri->addTriangleIndex(startIndex,endIndex); subTri->setName(groupName); subTri->showMaterials(baseMesh->materialsShown()); subTri->showNormals(baseMesh->normalsShown()); subTri->showTriNorms(baseMesh->triNormsShown()); //subTri->showColors(baseMesh->colorsShown()); //subTri->showWired(baseMesh->isShownAsWire()); baseMesh->addChild(subTri); } else { delete subTri; subTri = 0; objWarnings[NOT_ENOUGH_MEMORY] = true; } } baseMesh->setVisible(false); vertices->setLocked(true); } baseMesh->addChild(vertices); //DGM: we can't deactive the vertices if it has children! (such as polyline) if (vertices->getChildrenNumber() != 0) vertices->setVisible(false); else vertices->setEnabled(false); container.addChild(baseMesh); } if (!baseMesh && vertices->size() != 0) { //no (valid) mesh! container.addChild(vertices); //we hide the vertices if the entity has children (probably polylines!) if (vertices->getChildrenNumber() != 0) { vertices->setVisible(false); } } //special case: normals held by cloud! if (normals && !normalsPerFacet) { if (normsRead == pointsRead) //must be 'per-vertex' normals { vertices->setNormsTable(normals); if (baseMesh) baseMesh->showNormals(true); } else { ccLog::Warning("File contains normals which seem to be neither per-vertex nor per-face!!! We had to ignore them..."); } } } if (error) { if (baseMesh) delete baseMesh; if (vertices) delete vertices; } //release shared structures if (normals) { normals->release(); normals = 0; } if (texCoords) { texCoords->release(); texCoords = 0; } if (materials) { materials->release(); materials = 0; } pDlg.close(); //potential warnings if (objWarnings[INVALID_NORMALS]) ccLog::Warning("[OBJ] Some normals in file were invalid. You should re-compute them (select entity, then \"Edit > Normals > Compute\")"); if (objWarnings[INVALID_INDEX]) ccLog::Warning("[OBJ] File is malformed! Check indexes..."); if (objWarnings[NOT_ENOUGH_MEMORY]) ccLog::Warning("[OBJ] Not enough memory!"); if (objWarnings[INVALID_LINE]) ccLog::Warning("[OBJ] File is malformed! Missing data."); if (error) { if (objWarnings[NOT_ENOUGH_MEMORY]) return CC_FERR_NOT_ENOUGH_MEMORY; else if (objWarnings[CANCELLED_BY_USER]) return CC_FERR_CANCELED_BY_USER; else return CC_FERR_MALFORMED_FILE; } else { return CC_FERR_NO_ERROR; } }