void ccGenericMesh::applyGLTransformation(const ccGLMatrix& trans) { //vertices should be handled another way! //we must take care of the triangle normals! if (m_triNormals && (!getParent() || !getParent()->isKindOf(CC_MESH))) { bool recoded = false; //if there is more triangle normals than the size of the compressed //normals array, we recompress the array instead of recompressing each normal unsigned i,numTriNormals = m_triNormals->currentSize(); if (numTriNormals>ccNormalVectors::GetNumberOfVectors()) { NormsIndexesTableType* newNorms = new NormsIndexesTableType; if (newNorms->reserve(ccNormalVectors::GetNumberOfVectors())) { for (i=0; i<ccNormalVectors::GetNumberOfVectors(); i++) { CCVector3 new_n(ccNormalVectors::GetNormal(i)); trans.applyRotation(new_n); normsType newNormIndex = ccNormalVectors::GetNormIndex(new_n.u); newNorms->addElement(newNormIndex); } m_triNormals->placeIteratorAtBegining(); for (i=0; i<numTriNormals; i++) { m_triNormals->setValue(i,newNorms->getValue(m_triNormals->getCurrentValue())); m_triNormals->forwardIterator(); } recoded=true; } newNorms->clear(); newNorms->release(); newNorms=0; } //if there is less triangle normals than the compressed normals array size //(or if there is not enough memory to instantiate the temporary array), //we recompress each normal ... if (!recoded) { //on recode direct chaque normale m_triNormals->placeIteratorAtBegining(); for (i=0; i<numTriNormals; i++) { normsType* _theNormIndex = m_triNormals->getCurrentValuePtr(); CCVector3 new_n(ccNormalVectors::GetNormal(*_theNormIndex)); trans.applyRotation(new_n.u); *_theNormIndex = ccNormalVectors::GetNormIndex(new_n.u); m_triNormals->forwardIterator(); } } } else { //TODO: process failed! } }
const ccGenericPrimitive& ccGenericPrimitive::operator += (const ccGenericPrimitive& prim) { ccPointCloud* verts = vertices(); unsigned vertCount = verts->size(); unsigned facesCount = size(); unsigned triFacesNormCount = (m_triNormals ? m_triNormals->currentSize() : 0); //count new number of vertices & faces unsigned newVertCount = vertCount + prim.getAssociatedCloud()->size(); unsigned newFacesCount = facesCount + prim.size(); bool primHasVertNorms = prim.getAssociatedCloud()->hasNormals(); bool primHasFaceNorms = prim.hasTriNormals(); //reserve memory if (verts->reserve(newVertCount) && (!primHasVertNorms || verts->reserveTheNormsTable()) && reserve(newFacesCount) && (!primHasFaceNorms || m_triNormalIndexes || reservePerTriangleNormalIndexes())) { //copy vertices & normals ccGenericPointCloud* cloud = prim.getAssociatedCloud(); unsigned i; for (i=0;i<cloud->size();++i) { verts->addPoint(*cloud->getPoint(i)); if (primHasVertNorms) verts->addNormIndex(cloud->getPointNormalIndex(i)); } //copy face normals if (primHasFaceNorms) { const NormsIndexesTableType* primNorms = prim.getTriNormsTable(); assert(primNorms); unsigned primTriNormCount = primNorms->currentSize(); NormsIndexesTableType* normsTable = (m_triNormals ? m_triNormals : new NormsIndexesTableType()); if (!normsTable || !normsTable->reserve(triFacesNormCount+primTriNormCount)) { ccLog::Error("[ccGenericPrimitive::operator +] Not enough memory!"); return *this; } //attach table if not done already if (!m_triNormals) { setTriNormsTable(normsTable); assert(m_triNormals); //primitives must have their normal table as child! addChild(m_triNormals); } for (unsigned i=0; i<primTriNormCount; ++i) normsTable->addElement(primNorms->getValue(i)); } //copy faces for (i=0;i<prim.size();++i) { const CCLib::TriangleSummitsIndexes* tsi = prim.getTriangleIndexes(i); addTriangle(vertCount+tsi->i1,vertCount+tsi->i2,vertCount+tsi->i3); if (primHasFaceNorms) { const int* normIndexes = prim.m_triNormalIndexes->getValue(i); assert(normIndexes); addTriangleNormalIndexes(triFacesNormCount+normIndexes[0],triFacesNormCount+normIndexes[1],triFacesNormCount+normIndexes[2]); } } } else { ccLog::Error("[ccGenericPrimitive::operator +] Not enough memory!"); } return *this; }
//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; } }
bool ccFacet::createInternalRepresentation( CCLib::GenericIndexedCloudPersist* points, const PointCoordinateType* planeEquation/*=0*/) { assert(points); if (!points) return false; unsigned ptsCount = points->size(); if (ptsCount < 3) return false; CCLib::Neighbourhood Yk(points); //get corresponding plane if (!planeEquation) { planeEquation = Yk.getLSPlane(); if (!planeEquation) { ccLog::Warning("[ccFacet::createInternalRepresentation] Failed to compute the LS plane passing through the input points!"); return false; } } memcpy(m_planeEquation, planeEquation, sizeof(PointCoordinateType) * 4); //we project the input points on a plane std::vector<CCLib::PointProjectionTools::IndexedCCVector2> points2D; CCVector3 X, Y; //local base if (!Yk.projectPointsOn2DPlane<CCLib::PointProjectionTools::IndexedCCVector2>(points2D, nullptr, &m_center, &X, &Y)) { ccLog::Error("[ccFacet::createInternalRepresentation] Not enough memory!"); return false; } //compute resulting RMS m_rms = CCLib::DistanceComputationTools::computeCloud2PlaneDistanceRMS(points, m_planeEquation); //update the points indexes (not done by Neighbourhood::projectPointsOn2DPlane) { for (unsigned i = 0; i < ptsCount; ++i) { points2D[i].index = i; } } //try to get the points on the convex/concave hull to build the contour and the polygon { std::list<CCLib::PointProjectionTools::IndexedCCVector2*> hullPoints; if (!CCLib::PointProjectionTools::extractConcaveHull2D( points2D, hullPoints, m_maxEdgeLength*m_maxEdgeLength)) { ccLog::Error("[ccFacet::createInternalRepresentation] Failed to compute the convex hull of the input points!"); } unsigned hullPtsCount = static_cast<unsigned>(hullPoints.size()); //create vertices m_contourVertices = new ccPointCloud(); { if (!m_contourVertices->reserve(hullPtsCount)) { delete m_contourVertices; m_contourVertices = nullptr; ccLog::Error("[ccFacet::createInternalRepresentation] Not enough memory!"); return false; } //projection on the LS plane (in 3D) for (std::list<CCLib::PointProjectionTools::IndexedCCVector2*>::const_iterator it = hullPoints.begin(); it != hullPoints.end(); ++it) { m_contourVertices->addPoint(m_center + X*(*it)->x + Y*(*it)->y); } m_contourVertices->setName(DEFAULT_CONTOUR_POINTS_NAME); m_contourVertices->setLocked(true); m_contourVertices->setEnabled(false); addChild(m_contourVertices); } //we create the corresponding (3D) polyline { m_contourPolyline = new ccPolyline(m_contourVertices); if (m_contourPolyline->reserve(hullPtsCount)) { m_contourPolyline->addPointIndex(0, hullPtsCount); m_contourPolyline->setClosed(true); m_contourPolyline->setVisible(true); m_contourPolyline->setLocked(true); m_contourPolyline->setName(DEFAULT_CONTOUR_NAME); m_contourVertices->addChild(m_contourPolyline); m_contourVertices->setEnabled(true); m_contourVertices->setVisible(false); } else { delete m_contourPolyline; m_contourPolyline = nullptr; ccLog::Warning("[ccFacet::createInternalRepresentation] Not enough memory to create the contour polyline!"); } } //we create the corresponding (2D) mesh std::vector<CCVector2> hullPointsVector; try { hullPointsVector.reserve(hullPoints.size()); for (std::list<CCLib::PointProjectionTools::IndexedCCVector2*>::const_iterator it = hullPoints.begin(); it != hullPoints.end(); ++it) { hullPointsVector.push_back(**it); } } catch (...) { ccLog::Warning("[ccFacet::createInternalRepresentation] Not enough memory to create the contour mesh!"); } //if we have computed a concave hull, we must remove triangles falling outside! bool removePointsOutsideHull = (m_maxEdgeLength > 0); if (!hullPointsVector.empty() && CCLib::Delaunay2dMesh::Available()) { //compute the facet surface CCLib::Delaunay2dMesh dm; char errorStr[1024]; if (dm.buildMesh(hullPointsVector, 0, errorStr)) { if (removePointsOutsideHull) dm.removeOuterTriangles(hullPointsVector, hullPointsVector); unsigned triCount = dm.size(); assert(triCount != 0); m_polygonMesh = new ccMesh(m_contourVertices); if (m_polygonMesh->reserve(triCount)) { //import faces for (unsigned i = 0; i < triCount; ++i) { const CCLib::VerticesIndexes* tsi = dm.getTriangleVertIndexes(i); m_polygonMesh->addTriangle(tsi->i1, tsi->i2, tsi->i3); } m_polygonMesh->setVisible(true); m_polygonMesh->enableStippling(true); //unique normal for facets if (m_polygonMesh->reservePerTriangleNormalIndexes()) { NormsIndexesTableType* normsTable = new NormsIndexesTableType(); normsTable->reserve(1); CCVector3 N(m_planeEquation); normsTable->addElement(ccNormalVectors::GetNormIndex(N.u)); m_polygonMesh->setTriNormsTable(normsTable); for (unsigned i = 0; i < triCount; ++i) m_polygonMesh->addTriangleNormalIndexes(0, 0, 0); //all triangles will have the same normal! m_polygonMesh->showNormals(true); m_polygonMesh->setLocked(true); m_polygonMesh->setName(DEFAULT_POLYGON_MESH_NAME); m_contourVertices->addChild(m_polygonMesh); m_contourVertices->setEnabled(true); m_contourVertices->setVisible(false); } else { ccLog::Warning("[ccFacet::createInternalRepresentation] Not enough memory to create the polygon mesh's normals!"); } //update facet surface m_surface = CCLib::MeshSamplingTools::computeMeshArea(m_polygonMesh); } else { delete m_polygonMesh; m_polygonMesh = nullptr; ccLog::Warning("[ccFacet::createInternalRepresentation] Not enough memory to create the polygon mesh!"); } } else { ccLog::Warning(QString("[ccFacet::createInternalRepresentation] Failed to create the polygon mesh (third party lib. said '%1'").arg(errorStr)); } } } return true; }
virtual void add3dFace(const DL_3dFaceData& face) { //TODO: understand what this really is?! CCVector3 P[4]; for (unsigned i=0; i<4; ++i) { P[i] = CCVector3( static_cast<PointCoordinateType>(face.x[i]), static_cast<PointCoordinateType>(face.y[i]), static_cast<PointCoordinateType>(face.z[i]) ); } //create the 'faces' mesh if necessary if (!m_faces) { ccPointCloud* vertices = new ccPointCloud("vertices"); m_faces = new ccMesh(vertices); m_faces->setName("Faces"); m_faces->addChild(vertices); m_faces->setVisible(true); vertices->setEnabled(false); vertices->setLocked(true); m_root->addChild(m_faces); } ccPointCloud* vertices = dynamic_cast<ccPointCloud*>(m_faces->getAssociatedCloud()); if (!vertices) { assert(false); return; } int vertIndexes[4] = {-1, -1, -1, -1}; unsigned addedVertCount = 4; //check if the two last vertices are the same if (P[2].x == P[3].x && P[2].y == P[3].y && P[2].z == P[3].z) addedVertCount = 3; //current face color colorType col[3]; colorType* faceCol = 0; if (getCurrentColour(col)) faceCol = col; //look for already defined vertices unsigned vertCount = vertices->size(); if (vertCount) { //DGM TODO: could we be smarter? for (unsigned i=0; i<addedVertCount; ++i) { for (unsigned j=0; j<vertCount; ++j) { const CCVector3* Pj = vertices->getPoint(j); if (P[i].x == Pj->x && P[i].y == Pj->y && P[i].z == Pj->z) { bool useCurrentVertex = true; //We must also check that the color is the same (if any) if (faceCol || vertices->hasColors()) { const colorType* _faceCol = faceCol ? faceCol : ccColor::white; const colorType* _vertCol = vertices->hasColors() ? vertices->getPointColor(j) : ccColor::white; useCurrentVertex = (_faceCol[0] == _vertCol[0] && _faceCol[1] == _vertCol[1] && _faceCol[2] == _vertCol[2]); } if (useCurrentVertex) { vertIndexes[i] = static_cast<int>(j); break; } } } } } //now create new vertices unsigned createdVertCount = 0; { for (unsigned i=0; i<addedVertCount; ++i) if (vertIndexes[i] < 0) ++createdVertCount; } if (createdVertCount != 0) { //reserve memory for the new vertices if (!vertices->reserve(vertCount+createdVertCount)) { ccLog::Error("[DxfImporter] Not enough memory!"); return; } for (unsigned i=0; i<addedVertCount; ++i) { if (vertIndexes[i] < 0) { vertIndexes[i] = static_cast<int>(vertCount++); vertices->addPoint(P[i]); } } } //number of triangles to add unsigned addTriCount = (addedVertCount == 3 ? 1 : 2); //now add the corresponding face(s) if (!m_faces->reserve(m_faces->size() + addTriCount)) { ccLog::Error("[DxfImporter] Not enough memory!"); return; } m_faces->addTriangle(vertIndexes[0], vertIndexes[1], vertIndexes[2]); if (addedVertCount == 4) m_faces->addTriangle(vertIndexes[0], vertIndexes[2], vertIndexes[3]); //add per-triangle normals { //normals table NormsIndexesTableType* triNormsTable = m_faces->getTriNormsTable(); bool firstTime = false; if (!triNormsTable) { triNormsTable = new NormsIndexesTableType(); m_faces->setTriNormsTable(triNormsTable); m_faces->addChild(triNormsTable); firstTime = true; } //add 1 or 2 new entries unsigned triNormCount = triNormsTable->currentSize(); if (!triNormsTable->reserve(triNormsTable->currentSize() + addTriCount)) { ccLog::Error("[DxfImporter] Not enough memory!"); return; } CCVector3 N = (P[1]-P[0]).cross(P[2]-P[0]); N.normalize(); triNormsTable->addElement(ccNormalVectors::GetNormIndex(N.u)); if (addTriCount == 2) { N = (P[2]-P[0]).cross(P[3]-P[0]); N.normalize(); triNormsTable->addElement(ccNormalVectors::GetNormIndex(N.u)); } //per-triangle normals indexes if (firstTime) { if (!m_faces->reservePerTriangleNormalIndexes()) { ccLog::Error("[DxfImporter] Not enough memory!"); return; } m_faces->showNormals(true); } int n1 = static_cast<int>(triNormCount); m_faces->addTriangleNormalIndexes(n1, n1, n1); if (addTriCount == 2) { int n2 = static_cast<int>(triNormCount+1); m_faces->addTriangleNormalIndexes(n2, n2, n2); } } //and now for the color if (faceCol) { //RGB field already instantiated? if (vertices->hasColors()) { for (unsigned i=0; i<createdVertCount; ++i) vertices->addRGBColor(faceCol); } //otherwise, reserve memory and set all previous points to white by default else if (vertices->setRGBColor(ccColor::white)) { //then replace the last color(s) by the current one for (unsigned i=0; i<createdVertCount; ++i) vertices->setPointColor(vertCount-1-i,faceCol); m_faces->showColors(true); } } else if (vertices->hasColors()) { //add default color if none is defined! for (unsigned i=0; i<createdVertCount; ++i) vertices->addRGBColor(ccColor::white); } }