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; } }
//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; }
//==================================================loadFile=================================================// CC_FILE_ERROR PlyFilter::loadFile(QString filename, ccHObject& container, LoadParameters& parameters) { //reset statics! s_triCount = 0;//三角面片的个数 s_unsupportedPolygonType = false;//支持多边形类型 s_texCoordCount = 0;//纹理坐标个数 s_invalidTexCoordinates = false;//纹理坐标无效 s_totalScalarCount = 0;// s_IntensityCount = 0;// s_ColorCount = 0;//颜色个数 s_NormalCount = 0;//法向量个数 s_PointCount = 0;//点的个数 s_PointDataCorrupted = false; s_loadParameters = parameters; s_Pshift = CCVector3d(0,0,0); /****************/ /*** Header ***/ /****************/ //open a PLY file for reading p_ply ply = ply_open(qPrintable(filename), NULL, 0, NULL); if (!ply) return CC_FERR_READING; //ccLog::PrintDebug(QString("[PLY] Opening file '%1' ...").arg(filename)); ccLog::PrintDebug(QString("[PLY] 打开文件 '%1' ...").arg(filename)); if (!ply_read_header(ply)) { ply_close(ply); return CC_FERR_WRONG_FILE_TYPE; } //storage mode: little/big endian e_ply_storage_mode storage_mode; get_plystorage_mode(ply,&storage_mode); /*****************/ /*** Texture ***/ /*****************/ //eventual texture file declared in the comments (keyword: TEXTUREFILE) QString textureFileName; //texture coordinates TextureCoordsContainer* texCoords = 0; /******************/ /*** Comments ***/ /******************/ { const char* lastComment = NULL; //display comments while ((lastComment = ply_get_next_comment(ply, lastComment))) { ccLog::Print("[PLY][Comment] %s",lastComment); //specific case: TextureFile 'filename.ext' if (QString(lastComment).toUpper().startsWith("TEXTUREFILE ")) textureFileName = QString(lastComment).mid(12).trimmed(); } } /*******************************/ /*** Elements & properties ***/ /*******************************/ //Point-based elements (points, colors, normals, etc.) std::vector<plyElement> pointElements; //Mesh-based elements (vertices, etc.) std::vector<plyElement> meshElements; //Point-based element properties (coordinates, color components, etc.) std::vector<plyProperty> stdProperties; //Mesh-based element properties (vertex indexes, etc.) std::vector<plyProperty> listProperties; //last read element plyElement lastElement; lastElement.elem = 0; while ((lastElement.elem = ply_get_next_element(ply, lastElement.elem))) { //we get next element info ply_get_element_info(lastElement.elem, &lastElement.elementName, &lastElement.elementInstances); if (lastElement.elementInstances == 0) { ccLog::Warning("[PLY] Element '%s' was ignored as it has 0 instance!",lastElement.elementName); continue; } lastElement.properties.clear(); lastElement.propertiesCount=0; lastElement.isList=false; //printf("Element: %s\n",lastElement.elementName); //last read property plyProperty lastProperty; lastProperty.prop = 0; lastProperty.elemIndex = 0; while ((lastProperty.prop = ply_get_next_property(lastElement.elem,lastProperty.prop))) { //we get next property info ply_get_property_info(lastProperty.prop, &lastProperty.propName, &lastProperty.type, &lastProperty.length_type, &lastProperty.value_type); //printf("\tProperty: %s (%s)\n",lastProperty.propName,e_ply_type_names[lastProperty.type]); if (lastProperty.type == 16) //PLY_LIST lastElement.isList = true; lastElement.properties.push_back(lastProperty); ++lastElement.propertiesCount; } //if we have a "mesh-like" element if (lastElement.isList) { //we store its properties in 'listProperties' for (size_t i=0; i<lastElement.properties.size(); ++i) { plyProperty& prop = lastElement.properties[i]; prop.elemIndex = (int)meshElements.size(); //we only keep track of lists (we can't handle per triangle scalars) if (prop.type == 16) listProperties.push_back(prop); else { ccLog::Warning("[PLY] Unhandled property: [%s:%s] (%s)", lastElement.elementName, prop.propName, e_ply_type_names[prop.type]); } } meshElements.push_back(lastElement); } else //else if we have a "point-like" element { //we store its properties in 'stdProperties' for (size_t i=0; i<lastElement.properties.size(); ++i) { plyProperty& prop = lastElement.properties[i]; prop.elemIndex = (int)pointElements.size(); stdProperties.push_back(prop); } pointElements.push_back(lastElement); } } //We need some points at least! if (pointElements.empty()) { ply_close(ply); return CC_FERR_NO_LOAD; } /**********************/ /*** Objects info ***/ /**********************/ { const char* lastObjInfo = NULL; while ((lastObjInfo = ply_get_next_obj_info(ply, lastObjInfo))) ccLog::Print("[PLY][Info] %s",lastObjInfo); } /****************/ /*** Dialog ***/ /****************/ //properties indexes (0 = unassigned) static const unsigned nStdProp = 10; int stdPropIndexes[nStdProp] = {0,0,0,0,0,0,0,0,0,0}; int& xIndex = stdPropIndexes[0]; int& yIndex = stdPropIndexes[1]; int& zIndex = stdPropIndexes[2]; int& nxIndex = stdPropIndexes[3]; int& nyIndex = stdPropIndexes[4]; int& nzIndex = stdPropIndexes[5]; int& rIndex = stdPropIndexes[6]; int& gIndex = stdPropIndexes[7]; int& bIndex = stdPropIndexes[8]; int& iIndex = stdPropIndexes[9]; std::vector<int> sfPropIndexes; //int& sfIndex = stdPropIndexes[10]; static const unsigned nListProp = 2; int listPropIndexes[nListProp] = {0,0}; int& facesIndex = listPropIndexes[0]; int& texCoordsIndex = listPropIndexes[1]; //Combo box items for standard properties (coordinates, color components, etc.) QStringList stdPropsText; stdPropsText << QString("None"); { for (int i=1; i<=static_cast<int>(stdProperties.size()); ++i) { plyProperty& pp = stdProperties[i-1]; QString itemText = QString("%1 - %2 [%3]").arg(pointElements[pp.elemIndex].elementName).arg(pp.propName).arg(e_ply_type_names[pp.type]); assert(pp.type!=16); //we don't want any PLY_LIST here stdPropsText << itemText; QString elementName = QString(pointElements[pp.elemIndex].elementName).toUpper(); QString propName = QString(pp.propName).toUpper(); if (nxIndex == 0 && (propName.contains("NX") || (elementName.contains("NORM") && propName.endsWith("X")))) nxIndex = i; else if (nyIndex == 0 && (propName.contains("NY") || (elementName.contains("NORM") && propName.endsWith("Y")))) nyIndex = i; else if (nzIndex == 0 && (propName.contains("NZ") || (elementName.contains("NORM") && propName.endsWith("Z")))) nzIndex = i; else if (rIndex == 0 && (propName.contains("RED") || (elementName.contains("COL") && propName.endsWith("R")))) rIndex = i; else if (gIndex == 0 && (propName.contains("GREEN") || (elementName.contains("COL") && propName.endsWith("G")))) gIndex = i; else if (bIndex == 0 && (propName.contains("BLUE") || (elementName.contains("COL") && propName.endsWith("B")))) bIndex = i; else if (iIndex == 0 && (propName.contains("INTENSITY") || propName.contains("GRAY") || propName.contains("GREY") || (elementName.contains("COL") && propName.endsWith("I")))) iIndex = i; else if (elementName.contains("VERT") || elementName.contains("POINT")) { if (propName.contains("SCAL")) sfPropIndexes.push_back(i); else if (xIndex == 0 && propName.endsWith("X")) xIndex = i; else if (yIndex == 0 && propName.endsWith("Y")) yIndex = i; else if (zIndex == 0 && propName.endsWith("Z")) zIndex = i; } else if (propName.contains("SCAL") || propName.contains("VAL")) sfPropIndexes.push_back(i); } } //Combo box items for list properties (vertex indexes, etc.) QStringList listPropsText; { listPropsText << QString("None"); for (int i=0; i<static_cast<int>(listProperties.size()); ++i) { plyProperty& pp = listProperties[i]; QString itemText = QString("%0 - %1 [%2]").arg(meshElements[pp.elemIndex].elementName).arg(pp.propName).arg(e_ply_type_names[pp.type]); assert(pp.type==16); //we only want PLY_LIST here listPropsText << itemText; QString elementName = QString(meshElements[pp.elemIndex].elementName).toUpper(); QString propName = QString(pp.propName).toUpper(); if (elementName.contains("FACE") || elementName.contains("TRI")) { if (facesIndex == 0 && propName.contains("IND")) facesIndex = i+1; if (texCoordsIndex == 0 && propName.contains("COORD")) texCoordsIndex = i+1; } } } //combo-box max visible items int stdPropsCount = stdPropsText.count(); int listPropsCount = listPropsText.count(); //we need at least 2 coordinates! if (stdPropsCount < 2) { ccLog::Warning("[PLY] This ply file has less than 2 properties defined! (not even X and Y ;)"); return CC_FERR_MALFORMED_FILE; } else if (stdPropsCount < 4 && !parameters.alwaysDisplayLoadDialog) { //brute force heuristic xIndex = 1; yIndex = 2; zIndex = (stdPropsCount > 3 ? 3 : 0); facesIndex = (listPropsCount > 1 ? 1 : 0); } else { //we count all assigned properties int assignedStdProperties = 0; { for (unsigned i=0; i<nStdProp; ++i) if (stdPropIndexes[i] > 0) ++assignedStdProperties; } int assignedListProperties = 0; { for (unsigned i=0; i<nListProp; ++i) if (listPropIndexes[i] > 0) ++assignedListProperties; } if ( parameters.alwaysDisplayLoadDialog || stdPropsCount > assignedStdProperties+1 //+1 because of the first item in the combo box ('none') || listPropsCount > assignedListProperties+1 ) //+1 because of the first item in the combo box ('none') { PlyOpenDlg pod/*(MainWindow::TheInstance())*/; pod.plyTypeEdit->setText(e_ply_storage_mode_names[storage_mode]); pod.elementsEdit->setText(QString::number(pointElements.size())); pod.propertiesEdit->setText(QString::number(listProperties.size()+stdProperties.size())); //we fill all combo-boxes with all items pod.setDefaultComboItems(stdPropsText); pod.setListComboItems(listPropsText); //try to restore previous context (if any) bool hasAPreviousContext = false; if (!pod.restorePreviousContext(hasAPreviousContext)) { if (hasAPreviousContext) ccLog::Warning("[PLY] Too many differences with the previous file, we reset the dialog."); //Set default/guessed element pod.xComboBox->setCurrentIndex(xIndex); pod.yComboBox->setCurrentIndex(yIndex); pod.zComboBox->setCurrentIndex(zIndex); pod.rComboBox->setCurrentIndex(rIndex); pod.gComboBox->setCurrentIndex(gIndex); pod.bComboBox->setCurrentIndex(bIndex); pod.iComboBox->setCurrentIndex(iIndex); pod.sfComboBox->setCurrentIndex(sfPropIndexes.empty() ? 0 : sfPropIndexes.front()); for (size_t j=1; j<sfPropIndexes.size(); ++j) pod.addSFComboBox(sfPropIndexes[j]); pod.nxComboBox->setCurrentIndex(nxIndex); pod.nyComboBox->setCurrentIndex(nyIndex); pod.nzComboBox->setCurrentIndex(nzIndex); pod.facesComboBox->setCurrentIndex(facesIndex); pod.textCoordsComboBox->setCurrentIndex(texCoordsIndex); } //We show the dialog (or we try to skip it ;) if (parameters.alwaysDisplayLoadDialog && !pod.canBeSkipped() && !pod.exec()) { ply_close(ply); return CC_FERR_CANCELED_BY_USER; } //Force events processing (to hide dialog) QCoreApplication::processEvents(); xIndex = pod.xComboBox->currentIndex(); yIndex = pod.yComboBox->currentIndex(); zIndex = pod.zComboBox->currentIndex(); nxIndex = pod.nxComboBox->currentIndex(); nyIndex = pod.nyComboBox->currentIndex(); nzIndex = pod.nzComboBox->currentIndex(); rIndex = pod.rComboBox->currentIndex(); gIndex = pod.gComboBox->currentIndex(); bIndex = pod.bComboBox->currentIndex(); iIndex = pod.iComboBox->currentIndex(); facesIndex = pod.facesComboBox->currentIndex(); texCoordsIndex = pod.textCoordsComboBox->currentIndex(); //get (non null) SF properties sfPropIndexes.clear(); { for (size_t j=0; j<pod.m_sfCombos.size(); ++j) if (pod.m_sfCombos[j]->currentIndex() > 0) sfPropIndexes.push_back(pod.m_sfCombos[j]->currentIndex()); } } } /*************************/ /*** Callbacks setup ***/ /*************************/ //Main point cloud ccPointCloud* cloud = new ccPointCloud("unnamed - Cloud"); /* POINTS (X,Y,Z) */ unsigned numberOfPoints = 0; assert(xIndex != yIndex && xIndex != zIndex && yIndex != zIndex); //POINTS (X) if (xIndex > 0) { long flags = ELEM_POS_0; //X coordinate if (xIndex > yIndex && xIndex > zIndex) flags |= ELEM_EOL; plyProperty& pp = stdProperties[xIndex-1]; ply_set_read_cb(ply, pointElements[pp.elemIndex].elementName, pp.propName, vertex_cb, cloud, flags); numberOfPoints = pointElements[pp.elemIndex].elementInstances; } //POINTS (Y) if (yIndex > 0) { long flags = ELEM_POS_1; //Y coordinate if (yIndex > xIndex && yIndex > zIndex) flags |= ELEM_EOL; plyProperty& pp = stdProperties[yIndex-1]; ply_set_read_cb(ply, pointElements[pp.elemIndex].elementName, pp.propName, vertex_cb, cloud, flags); if (numberOfPoints > 0) { if ((long)numberOfPoints != pointElements[pp.elemIndex].elementInstances) { ccLog::Warning("[PLY] Bad/uncompatible assignation of point properties!"); delete cloud; ply_close(ply); return CC_FERR_BAD_ENTITY_TYPE; } } else numberOfPoints = pointElements[pp.elemIndex].elementInstances; } //POINTS (Z) if (zIndex > 0) { long flags = ELEM_POS_2; //Z coordinate if (zIndex > xIndex && zIndex > yIndex) flags |= ELEM_EOL; plyProperty& pp = stdProperties[zIndex-1]; ply_set_read_cb(ply, pointElements[pp.elemIndex].elementName, pp.propName, vertex_cb, cloud, flags); if (numberOfPoints > 0) { if ((long)numberOfPoints != pointElements[pp.elemIndex].elementInstances) { ccLog::Warning("[PLY] Bad/uncompatible assignation of point properties!"); delete cloud; ply_close(ply); return CC_FERR_BAD_ENTITY_TYPE; } } else numberOfPoints = pointElements[pp.elemIndex].elementInstances; } if (numberOfPoints == 0 || !cloud->reserveThePointsTable(numberOfPoints)) { delete cloud; ply_close(ply); return CC_FERR_NOT_ENOUGH_MEMORY; } /* NORMALS (X,Y,Z) */ unsigned numberOfNormals=0; assert(nxIndex == 0 || (nxIndex != nyIndex && nxIndex != nzIndex)); assert(nyIndex == 0 || (nyIndex != nxIndex && nyIndex != nzIndex)); assert(nzIndex == 0 || (nzIndex != nxIndex && nzIndex != nyIndex)); //NORMALS (X) if (nxIndex > 0) { long flags = ELEM_POS_0; //Nx if (nxIndex > nyIndex && nxIndex > nzIndex) flags |= ELEM_EOL; plyProperty& pp = stdProperties[nxIndex-1]; ply_set_read_cb(ply, pointElements[pp.elemIndex].elementName, pp.propName, normal_cb, cloud, flags); numberOfNormals = pointElements[pp.elemIndex].elementInstances; } //NORMALS (Y) if (nyIndex > 0) { long flags = ELEM_POS_1; //Ny if (nyIndex > nxIndex && nyIndex > nzIndex) flags |= ELEM_EOL; plyProperty& pp = stdProperties[nyIndex-1]; ply_set_read_cb(ply, pointElements[pp.elemIndex].elementName, pp.propName, normal_cb, cloud, flags); numberOfNormals = std::max(numberOfNormals, (unsigned)pointElements[pp.elemIndex].elementInstances); } //NORMALS (Z) if (nzIndex > 0) { long flags = ELEM_POS_2; //Nz if (nzIndex > nxIndex && nzIndex > nyIndex) flags |= ELEM_EOL; plyProperty& pp = stdProperties[nzIndex-1]; ply_set_read_cb(ply, pointElements[pp.elemIndex].elementName, pp.propName, normal_cb, cloud, flags); numberOfNormals = std::max(numberOfNormals, (unsigned)pointElements[pp.elemIndex].elementInstances); } //We check that the number of normals corresponds to the number of points if (numberOfNormals > 0) { if (numberOfPoints != numberOfNormals) { ccLog::Warning("[PLY] The number of normals doesn't match the number of points!"); delete cloud; ply_close(ply); return CC_FERR_BAD_ENTITY_TYPE; } if (!cloud->reserveTheNormsTable()) { delete cloud; ply_close(ply); return CC_FERR_NOT_ENOUGH_MEMORY; } cloud->showNormals(true); } /* COLORS (R,G,B) */ unsigned numberOfColors=0; assert(rIndex == 0 || (rIndex != gIndex && rIndex != bIndex)); assert(gIndex == 0 || (gIndex != rIndex && gIndex != bIndex)); assert(bIndex == 0 || (bIndex != rIndex && bIndex != gIndex)); if (rIndex > 0) { long flags = ELEM_POS_0; //R if (rIndex > gIndex && rIndex > bIndex) flags |= ELEM_EOL; plyProperty& pp = stdProperties[rIndex-1]; ply_set_read_cb(ply, pointElements[pp.elemIndex].elementName, pp.propName, rgb_cb, cloud, flags); numberOfColors = pointElements[pp.elemIndex].elementInstances; } if (gIndex > 0) { long flags = ELEM_POS_1; //G if (gIndex > rIndex && gIndex > bIndex) flags |= ELEM_EOL; plyProperty& pp = stdProperties[gIndex-1]; ply_set_read_cb(ply, pointElements[pp.elemIndex].elementName, pp.propName, rgb_cb, cloud, flags); numberOfColors = std::max(numberOfColors, (unsigned)pointElements[pp.elemIndex].elementInstances); } if (bIndex > 0) { long flags = ELEM_POS_2; //B if (bIndex > rIndex && bIndex > gIndex) flags |= ELEM_EOL; plyProperty& pp = stdProperties[bIndex-1]; ply_set_read_cb(ply, pointElements[pp.elemIndex].elementName, pp.propName, rgb_cb, cloud, flags); numberOfColors = std::max(numberOfColors, (unsigned)pointElements[pp.elemIndex].elementInstances); } /* Intensity (I) */ //INTENSITE (G) if (iIndex > 0) { if (numberOfColors > 0) { ccLog::Error("Can't import colors AND intensity (intensities will be ignored)!"); ccLog::Warning("[PLY] intensities will be ignored"); } else { plyProperty pp = stdProperties[iIndex-1]; ply_set_read_cb(ply, pointElements[pp.elemIndex].elementName, pp.propName, grey_cb, cloud, 0); numberOfColors = pointElements[pp.elemIndex].elementInstances; } } //We check that the number of colors corresponds to the number of points if (numberOfColors > 0) { if (numberOfPoints != numberOfColors) { ccLog::Warning("The number of colors doesn't match the number of points!"); delete cloud; ply_close(ply); return CC_FERR_BAD_ENTITY_TYPE; } if (!cloud->reserveTheRGBTable()) { delete cloud; ply_close(ply); return CC_FERR_NOT_ENOUGH_MEMORY; } cloud->showColors(true); } /* SCALAR FIELDS (SF) */ { for (size_t i=0; i<sfPropIndexes.size(); ++i) { int sfIndex = sfPropIndexes[i]; plyProperty& pp = stdProperties[sfIndex-1]; unsigned numberOfScalars = pointElements[pp.elemIndex].elementInstances; //does the number of scalars matches the number of points? if (numberOfPoints != numberOfScalars) { ccLog::Error(QString("Scalar field #%1: the number of scalars doesn't match the number of points (they will be ignored)!").arg(i+1)); ccLog::Warning(QString("[PLY] Scalar field #%1 ignored!").arg(i+1)); numberOfScalars = 0; } else { QString qPropName(pp.propName); if (qPropName.startsWith("scalar_") && qPropName.length() > 7) { //remove the 'scalar_' prefix added when saving SF with CC! qPropName = qPropName.mid(7).replace('_',' '); } int sfIdx = cloud->addScalarField(qPrintable(qPropName)); if (sfIdx >= 0) { CCLib::ScalarField* sf = cloud->getScalarField(sfIdx); assert(sf); if (sf->reserve(numberOfScalars)) { ply_set_read_cb(ply, pointElements[pp.elemIndex].elementName, pp.propName, scalar_cb, sf, 1); } else { cloud->deleteScalarField(sfIdx); sfIdx = -1; } } if (sfIdx < 0) { ccLog::Error(QString("Scalar field #%1: not enough memory to load scalar field (they will be ignored)!").arg(i+1)); ccLog::Warning(QString("[PLY] Scalar field #%1 ignored!").arg(i+1)); } } } } /* MESH FACETS (TRI) */ ccMesh* mesh = 0; unsigned numberOfFacets = 0; if (facesIndex > 0) { plyProperty& pp = listProperties[facesIndex-1]; assert(pp.type==16); //we only accept PLY_LIST here! mesh = new ccMesh(cloud); numberOfFacets = meshElements[pp.elemIndex].elementInstances; if (!mesh->reserve(numberOfFacets)) { ccLog::Error("Not enough memory to load facets (they will be ignored)!"); ccLog::Warning("[PLY] Mesh ignored!"); delete mesh; mesh = 0; numberOfFacets = 0; } else { ply_set_read_cb(ply, meshElements[pp.elemIndex].elementName, pp.propName, face_cb, mesh, 0); } } if (texCoordsIndex > 0) { plyProperty& pp = listProperties[texCoordsIndex-1]; assert(pp.type == 16); //we only accept PLY_LIST here! texCoords = new TextureCoordsContainer(); texCoords->link(); long numberOfCoordinates = meshElements[pp.elemIndex].elementInstances; assert(numberOfCoordinates == numberOfFacets); if (!texCoords->reserve(numberOfCoordinates*3)) { ccLog::Error("Not enough memory to load texture coordinates (they will be ignored)!"); ccLog::Warning("[PLY] Texture coordinates ignored!"); texCoords->release(); texCoords = 0; } else { ply_set_read_cb(ply, meshElements[pp.elemIndex].elementName, pp.propName, texCoords_cb, texCoords, 0); } } ccProgressDialog pDlg(false); if (parameters.alwaysDisplayLoadDialog) { //pDlg.setInfo("Loading in progress..."); //pDlg.setMethodTitle("PLY file"); pDlg.setInfo("正在加载..."); pDlg.setMethodTitle("PLY 文件"); pDlg.setRange(0,0); pDlg.show(); QApplication::processEvents(); } //let 'Rply' do the job;) int success = 0; try { success = ply_read(ply); } catch(...) { success = -1; } ply_close(ply); if (success < 1) { if (mesh) delete mesh; delete cloud; return CC_FERR_READING; } //we check mesh if (mesh && mesh->size() == 0) { if (s_unsupportedPolygonType) ccLog::Error("Mesh is not triangular! (unsupported)"); else ccLog::Error("Mesh is empty!"); delete mesh; mesh=0; } if (texCoords && (s_invalidTexCoordinates || s_texCoordCount != 3*mesh->size())) { ccLog::Error("Invalid texture coordinates! (they will be ignored)"); texCoords->release(); texCoords=0; } //we save parameters parameters = s_loadParameters; //we update scalar field(s) { for (unsigned i=0; i<cloud->getNumberOfScalarFields(); ++i) { CCLib::ScalarField* sf = cloud->getScalarField(i); assert(sf); sf->computeMinAndMax(); if (i == 0) { cloud->setCurrentDisplayedScalarField(0); cloud->showSF(true); } } } if (mesh) { assert(s_triCount > 0); //check number of loaded facets against 'theoretical' number if (s_triCount<numberOfFacets) { mesh->resize(s_triCount); ccLog::Warning("[PLY] Missing vertex indexes!"); } //check that vertex indices start at 0 unsigned minVertIndex=numberOfPoints,maxVertIndex=0; for (unsigned i=0;i<s_triCount;++i) { const CCLib::TriangleSummitsIndexes* tri = mesh->getTriangleIndexes(i); if (tri->i1 < minVertIndex) minVertIndex = tri->i1; else if (tri->i1 > maxVertIndex) maxVertIndex = tri->i1; if (tri->i2 < minVertIndex) minVertIndex = tri->i2; else if (tri->i2 > maxVertIndex) maxVertIndex = tri->i2; if (tri->i3 < minVertIndex) minVertIndex = tri->i3; else if (tri->i3 > maxVertIndex) maxVertIndex = tri->i3; } if (maxVertIndex>=numberOfPoints) { if (maxVertIndex == numberOfPoints && minVertIndex > 0) { ccLog::Warning("[PLY] Vertex indices seem to be shifted (+1)! We will try to 'unshift' indices (otherwise file is corrupted...)"); for (unsigned i=0;i<s_triCount;++i) { CCLib::TriangleSummitsIndexes* tri = mesh->getTriangleIndexes(i); --tri->i1; --tri->i2; --tri->i3; } } else //file is definitely corrupted! { ccLog::Warning("[PLY] Invalid vertex indices!"); delete mesh; delete cloud; return CC_FERR_MALFORMED_FILE; } } mesh->addChild(cloud); cloud->setEnabled(false); cloud->setName("Vertices"); //cloud->setLocked(true); //DGM: no need to lock it as it is only used by one mesh! //associated texture if (texCoords) { if (!textureFileName.isEmpty()) { QString textureFilePath = QFileInfo(filename).absolutePath() + QString('/') + textureFileName; ccMaterial::Shared material(new ccMaterial(textureFileName)); if (material->loadAndSetTexture(textureFilePath)) { if (mesh->reservePerTriangleTexCoordIndexes() && mesh->reservePerTriangleMtlIndexes()) { const QImage texture = material->getTexture(); ccLog::Print(QString("[PLY][Texture] Successfully loaded texture '%1' (%2x%3 pixels)").arg(textureFileName).arg(texture.width()).arg(texture.height())); //materials ccMaterialSet* materials = new ccMaterialSet("materials"); material->setDiffuse(ccColor::bright); material->setSpecular(ccColor::darker); material->setAmbient(ccColor::darker); materials->push_back(material); mesh->setMaterialSet(materials); mesh->setTexCoordinatesTable(texCoords); for (unsigned i=0;i<mesh->size();++i) { mesh->addTriangleMtlIndex(0); mesh->addTriangleTexCoordIndexes(i*3,i*3+1,i*3+2); } mesh->showMaterials(true); } else { ccLog::Warning("[PLY][Texture] Failed to reserve per-triangle texture coordinates! (not enough memory?)"); } } else { ccLog::Warning(QString("[PLY][Texture] Failed to load texture '%1'").arg(textureFilePath)); } } else { ccLog::Warning("[PLY][Texture] Texture coordinates loaded without an associated image! (we look for the 'TextureFile' keyword in comments)"); } } if (cloud->hasColors()) mesh->showColors(true); if (cloud->hasDisplayedScalarField()) mesh->showSF(true); if (cloud->hasNormals()) mesh->showNormals(true); else { //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!) //mesh->computeNormals(); ccLog::Warning("[PLY] Mesh has no normal! You can manually compute them (select it then call \"Edit > Normals > Compute\")"); } if (mesh->hasMaterials()) mesh->showNormals(false); container.addChild(mesh); } else { container.addChild(cloud); } if (texCoords) { texCoords->release(); texCoords = 0; } return CC_FERR_NO_ERROR; }