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! } }
void ccGenericMesh::drawMeOnly(CC_DRAW_CONTEXT& context) { ccGenericPointCloud* vertices = getAssociatedCloud(); if (!vertices) return; handleColorRamp(context); //3D pass if (MACRO_Draw3D(context)) { //any triangle? unsigned triNum = size(); if (triNum == 0) return; //L.O.D. bool lodEnabled = (triNum > GET_MAX_LOD_FACES_NUMBER() && context.decimateMeshOnMove && MACRO_LODActivated(context)); unsigned decimStep = (lodEnabled ? (unsigned)ceil((float)triNum*3 / (float)GET_MAX_LOD_FACES_NUMBER()) : 1); unsigned displayedTriNum = triNum / decimStep; //display parameters glDrawParams glParams; getDrawingParameters(glParams); glParams.showNorms &= bool(MACRO_LightIsEnabled(context)); //vertices visibility const ccGenericPointCloud::VisibilityTableType* verticesVisibility = vertices->getTheVisibilityArray(); bool visFiltering = (verticesVisibility && verticesVisibility->isAllocated()); //wireframe ? (not compatible with LOD) bool showWired = isShownAsWire() && !lodEnabled; //per-triangle normals? bool showTriNormals = (hasTriNormals() && triNormsShown()); //fix 'showNorms' glParams.showNorms = showTriNormals || (vertices->hasNormals() && m_normalsDisplayed); //materials & textures bool applyMaterials = (hasMaterials() && materialsShown()); bool showTextures = (hasTextures() && materialsShown() && !lodEnabled); //GL name pushing bool pushName = MACRO_DrawEntityNames(context); //special case: triangle names pushing (for picking) bool pushTriangleNames = MACRO_DrawTriangleNames(context); pushName |= pushTriangleNames; if (pushName) { //not fast at all! if (MACRO_DrawFastNamesOnly(context)) return; glPushName(getUniqueIDForDisplay()); //minimal display for picking mode! glParams.showNorms = false; glParams.showColors = false; //glParams.showSF --> we keep it only if SF 'NaN' values are hidden showTriNormals = false; applyMaterials = false; showTextures = false; } //in the case we need to display scalar field colors ccScalarField* currentDisplayedScalarField = 0; bool greyForNanScalarValues = true; unsigned colorRampSteps = 0; ccColorScale::Shared colorScale(0); if (glParams.showSF) { assert(vertices->isA(CC_TYPES::POINT_CLOUD)); ccPointCloud* cloud = static_cast<ccPointCloud*>(vertices); greyForNanScalarValues = (cloud->getCurrentDisplayedScalarField() && cloud->getCurrentDisplayedScalarField()->areNaNValuesShownInGrey()); if (greyForNanScalarValues && pushName) { //in picking mode, no need to take SF into account if we don't hide any points! glParams.showSF = false; } else { currentDisplayedScalarField = cloud->getCurrentDisplayedScalarField(); colorScale = currentDisplayedScalarField->getColorScale(); colorRampSteps = currentDisplayedScalarField->getColorRampSteps(); assert(colorScale); //get default color ramp if cloud has no scale associated?! if (!colorScale) colorScale = ccColorScalesManager::GetUniqueInstance()->getDefaultScale(ccColorScalesManager::BGYR); } } //materials or color? bool colorMaterial = false; if (glParams.showSF || glParams.showColors) { applyMaterials = false; colorMaterial = true; glColorMaterial(GL_FRONT_AND_BACK, GL_DIFFUSE); glEnable(GL_COLOR_MATERIAL); } //in the case we need to display vertex colors ColorsTableType* rgbColorsTable = 0; if (glParams.showColors) { if (isColorOverriden()) { glColor3ubv(m_tempColor); glParams.showColors = false; } else { assert(vertices->isA(CC_TYPES::POINT_CLOUD)); rgbColorsTable = static_cast<ccPointCloud*>(vertices)->rgbColors(); } } else { glColor3fv(context.defaultMat.diffuseFront); } if (glParams.showNorms) { //DGM: Strangely, when Qt::renderPixmap is called, the OpenGL version can fall to 1.0! glEnable((QGLFormat::openGLVersionFlags() & QGLFormat::OpenGL_Version_1_2 ? GL_RESCALE_NORMAL : GL_NORMALIZE)); glEnable(GL_LIGHTING); context.defaultMat.applyGL(true,colorMaterial); } //in the case we need normals (i.e. lighting) NormsIndexesTableType* normalsIndexesTable = 0; ccNormalVectors* compressedNormals = 0; if (glParams.showNorms) { assert(vertices->isA(CC_TYPES::POINT_CLOUD)); normalsIndexesTable = static_cast<ccPointCloud*>(vertices)->normals(); compressedNormals = ccNormalVectors::GetUniqueInstance(); } //stipple mask if (stipplingEnabled()) EnableGLStippleMask(true); if (!pushTriangleNames && !visFiltering && !(applyMaterials || showTextures) && (!glParams.showSF || greyForNanScalarValues)) { //the GL type depends on the PointCoordinateType 'size' (float or double) GLenum GL_COORD_TYPE = sizeof(PointCoordinateType) == 4 ? GL_FLOAT : GL_DOUBLE; glEnableClientState(GL_VERTEX_ARRAY); glVertexPointer(3,GL_COORD_TYPE,0,GetVertexBuffer()); if (glParams.showNorms) { glEnableClientState(GL_NORMAL_ARRAY); glNormalPointer(GL_COORD_TYPE,0,GetNormalsBuffer()); } if (glParams.showSF || glParams.showColors) { glEnableClientState(GL_COLOR_ARRAY); glColorPointer(3,GL_UNSIGNED_BYTE,0,GetColorsBuffer()); } //we can scan and process each chunk separately in an optimized way //we mimic the way ccMesh beahves by using virtual chunks! unsigned chunks = static_cast<unsigned>(ceil((double)displayedTriNum/(double)MAX_NUMBER_OF_ELEMENTS_PER_CHUNK)); unsigned chunkStart = 0; const colorType* col = 0; for (unsigned k=0; k<chunks; ++k, chunkStart += MAX_NUMBER_OF_ELEMENTS_PER_CHUNK) { //virtual chunk size const unsigned chunkSize = k+1 < chunks ? MAX_NUMBER_OF_ELEMENTS_PER_CHUNK : (displayedTriNum % MAX_NUMBER_OF_ELEMENTS_PER_CHUNK); //vertices PointCoordinateType* _vertices = GetVertexBuffer(); for (unsigned n=0; n<chunkSize; n+=decimStep) { const CCLib::TriangleSummitsIndexes* ti = getTriangleIndexes(chunkStart + n); memcpy(_vertices,vertices->getPoint(ti->i1)->u,sizeof(PointCoordinateType)*3); _vertices+=3; memcpy(_vertices,vertices->getPoint(ti->i2)->u,sizeof(PointCoordinateType)*3); _vertices+=3; memcpy(_vertices,vertices->getPoint(ti->i3)->u,sizeof(PointCoordinateType)*3); _vertices+=3; } //scalar field if (glParams.showSF) { colorType* _rgbColors = GetColorsBuffer(); assert(colorScale); for (unsigned n=0; n<chunkSize; n+=decimStep) { const CCLib::TriangleSummitsIndexes* ti = getTriangleIndexes(chunkStart + n); col = currentDisplayedScalarField->getValueColor(ti->i1); memcpy(_rgbColors,col,sizeof(colorType)*3); _rgbColors += 3; col = currentDisplayedScalarField->getValueColor(ti->i2); memcpy(_rgbColors,col,sizeof(colorType)*3); _rgbColors += 3; col = currentDisplayedScalarField->getValueColor(ti->i3); memcpy(_rgbColors,col,sizeof(colorType)*3); _rgbColors += 3; } } //colors else if (glParams.showColors) { colorType* _rgbColors = GetColorsBuffer(); for (unsigned n=0; n<chunkSize; n+=decimStep) { const CCLib::TriangleSummitsIndexes* ti = getTriangleIndexes(chunkStart + n); memcpy(_rgbColors,rgbColorsTable->getValue(ti->i1),sizeof(colorType)*3); _rgbColors += 3; memcpy(_rgbColors,rgbColorsTable->getValue(ti->i2),sizeof(colorType)*3); _rgbColors += 3; memcpy(_rgbColors,rgbColorsTable->getValue(ti->i3),sizeof(colorType)*3); _rgbColors += 3; } } //normals if (glParams.showNorms) { PointCoordinateType* _normals = GetNormalsBuffer(); if (showTriNormals) { for (unsigned n=0; n<chunkSize; n+=decimStep) { CCVector3 Na, Nb, Nc; getTriangleNormals(chunkStart + n, Na, Nb, Nc); memcpy(_normals,Na.u,sizeof(PointCoordinateType)*3); _normals+=3; memcpy(_normals,Nb.u,sizeof(PointCoordinateType)*3); _normals+=3; memcpy(_normals,Nc.u,sizeof(PointCoordinateType)*3); _normals+=3; } } else { for (unsigned n=0; n<chunkSize; n+=decimStep) { const CCLib::TriangleSummitsIndexes* ti = getTriangleIndexes(chunkStart + n); memcpy(_normals,vertices->getPointNormal(ti->i1).u,sizeof(PointCoordinateType)*3); _normals+=3; memcpy(_normals,vertices->getPointNormal(ti->i2).u,sizeof(PointCoordinateType)*3); _normals+=3; memcpy(_normals,vertices->getPointNormal(ti->i3).u,sizeof(PointCoordinateType)*3); _normals+=3; } } } if (!showWired) { glDrawArrays(lodEnabled ? GL_POINTS : GL_TRIANGLES,0,(chunkSize/decimStep)*3); } else { glDrawElements(GL_LINES,(chunkSize/decimStep)*6,GL_UNSIGNED_INT,GetWireVertexIndexes()); } } //disable arrays glDisableClientState(GL_VERTEX_ARRAY); if (glParams.showNorms) glDisableClientState(GL_NORMAL_ARRAY); if (glParams.showSF || glParams.showColors) glDisableClientState(GL_COLOR_ARRAY); } else { //current vertex color const colorType *col1=0,*col2=0,*col3=0; //current vertex normal const PointCoordinateType *N1=0,*N2=0,*N3=0; //current vertex texture coordinates float *Tx1=0,*Tx2=0,*Tx3=0; //loop on all triangles int lasMtlIndex = -1; if (showTextures) { //#define TEST_TEXTURED_BUNDLER_IMPORT #ifdef TEST_TEXTURED_BUNDLER_IMPORT glPushAttrib(GL_COLOR_BUFFER_BIT); glEnable(GL_BLEND); glBlendFunc(context.sourceBlend, context.destBlend); #endif glEnable(GL_TEXTURE_2D); } if (pushTriangleNames) glPushName(0); GLenum triangleDisplayType = lodEnabled ? GL_POINTS : showWired ? GL_LINE_LOOP : GL_TRIANGLES; glBegin(triangleDisplayType); //per-triangle normals const NormsIndexesTableType* triNormals = getTriNormsTable(); //materials const ccMaterialSet* materials = getMaterialSet(); for (unsigned n=0; n<triNum; ++n) { //current triangle vertices const CCLib::TriangleSummitsIndexes* tsi = getTriangleIndexes(n); //LOD: shall we display this triangle? if (n % decimStep) continue; if (visFiltering) { //we skip the triangle if at least one vertex is hidden if ((verticesVisibility->getValue(tsi->i1) != POINT_VISIBLE) || (verticesVisibility->getValue(tsi->i2) != POINT_VISIBLE) || (verticesVisibility->getValue(tsi->i3) != POINT_VISIBLE)) continue; } if (glParams.showSF) { assert(colorScale); col1 = currentDisplayedScalarField->getValueColor(tsi->i1); if (!col1) continue; col2 = currentDisplayedScalarField->getValueColor(tsi->i2); if (!col2) continue; col3 = currentDisplayedScalarField->getValueColor(tsi->i3); if (!col3) continue; } else if (glParams.showColors) { col1 = rgbColorsTable->getValue(tsi->i1); col2 = rgbColorsTable->getValue(tsi->i2); col3 = rgbColorsTable->getValue(tsi->i3); } if (glParams.showNorms) { if (showTriNormals) { assert(triNormals); int n1,n2,n3; getTriangleNormalIndexes(n,n1,n2,n3); N1 = (n1>=0 ? ccNormalVectors::GetNormal(triNormals->getValue(n1)).u : 0); N2 = (n1==n2 ? N1 : n1>=0 ? ccNormalVectors::GetNormal(triNormals->getValue(n2)).u : 0); N3 = (n1==n3 ? N1 : n3>=0 ? ccNormalVectors::GetNormal(triNormals->getValue(n3)).u : 0); } else { N1 = compressedNormals->getNormal(normalsIndexesTable->getValue(tsi->i1)).u; N2 = compressedNormals->getNormal(normalsIndexesTable->getValue(tsi->i2)).u; N3 = compressedNormals->getNormal(normalsIndexesTable->getValue(tsi->i3)).u; } } if (applyMaterials || showTextures) { assert(materials); int newMatlIndex = this->getTriangleMtlIndex(n); //do we need to change material? if (lasMtlIndex != newMatlIndex) { assert(newMatlIndex<(int)materials->size()); glEnd(); if (showTextures) { GLuint texID = (newMatlIndex>=0 ? (*materials)[newMatlIndex].texID : 0); if (texID>0) assert(glIsTexture(texID)); glBindTexture(GL_TEXTURE_2D, texID); } //if we don't have any current material, we apply default one (newMatlIndex>=0 ? (*materials)[newMatlIndex] : context.defaultMat).applyGL(glParams.showNorms,false); glBegin(triangleDisplayType); lasMtlIndex=newMatlIndex; } if (showTextures) { getTriangleTexCoordinates(n,Tx1,Tx2,Tx3); } } if (pushTriangleNames) { glEnd(); glLoadName(n); glBegin(triangleDisplayType); } else if (showWired) { glEnd(); glBegin(triangleDisplayType); } //vertex 1 if (N1) ccGL::Normal3v(N1); if (col1) glColor3ubv(col1); if (Tx1) glTexCoord2fv(Tx1); ccGL::Vertex3v(vertices->getPoint(tsi->i1)->u); //vertex 2 if (N2) ccGL::Normal3v(N2); if (col2) glColor3ubv(col2); if (Tx2) glTexCoord2fv(Tx2); ccGL::Vertex3v(vertices->getPoint(tsi->i2)->u); //vertex 3 if (N3) ccGL::Normal3v(N3); if (col3) glColor3ubv(col3); if (Tx3) glTexCoord2fv(Tx3); ccGL::Vertex3v(vertices->getPoint(tsi->i3)->u); } glEnd(); if (pushTriangleNames) glPopName(); if (showTextures) { #ifdef TEST_TEXTURED_BUNDLER_IMPORT glPopAttrib(); //GL_COLOR_BUFFER_BIT #endif glBindTexture(GL_TEXTURE_2D, 0); glDisable(GL_TEXTURE_2D); } } if (stipplingEnabled()) EnableGLStippleMask(false); if (colorMaterial) glDisable(GL_COLOR_MATERIAL); if (glParams.showNorms) { glDisable(GL_LIGHTING); glDisable((QGLFormat::openGLVersionFlags() & QGLFormat::OpenGL_Version_1_2 ? GL_RESCALE_NORMAL : GL_NORMALIZE)); } if (pushName) glPopName(); } }
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; }
bool ccGenericPrimitive::init(unsigned vertCount, bool vertNormals, unsigned faceCounts, unsigned faceNormCounts) { ccPointCloud* verts = vertices(); assert(verts); if (!verts) return false; /*** clear existing structures ***/ //clear vertices & normals verts->clear(); //takes care of vertices normals //clear triangles indexes assert(m_triVertIndexes); m_triVertIndexes->clear(); //clear per triangle normals removePerTriangleNormalIndexes(); if (m_triNormals) m_triNormals->clear(); //DGM: if we do this we'll have issues with the DB tree depending on where when we call this method! //{ // removeChild(m_triNormals); // setTriNormsTable(0); // assert(!m_triNormals); //} /*** init necessary structures ***/ if (vertCount && !verts->reserve(vertCount)) return false; if (vertNormals && !verts->reserveTheNormsTable()) { verts->clear(); return false; } if (faceCounts && !reserve(faceCounts)) { verts->clear(); return false; } if (faceNormCounts) { NormsIndexesTableType* normsTable = (m_triNormals ? m_triNormals : new NormsIndexesTableType()); if (!normsTable || !normsTable->reserve(faceNormCounts) || !reservePerTriangleNormalIndexes()) { verts->clear(); m_triVertIndexes->clear(); if (normsTable) delete normsTable; return false; } //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); } } return true; }
//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; }
// Converts a CC mesh to an FBX mesh static FbxNode* ToFbxMesh(ccGenericMesh* mesh, FbxScene* pScene) { if (!mesh) return 0; FbxMesh* lMesh = FbxMesh::Create(pScene, qPrintable(mesh->getName())); ccGenericPointCloud* cloud = mesh->getAssociatedCloud(); if (!cloud) return 0; unsigned vertCount = cloud->size(); unsigned faceCount = mesh->size(); // Create control points. { lMesh->InitControlPoints(vertCount); FbxVector4* lControlPoints = lMesh->GetControlPoints(); for (unsigned i=0; i<vertCount; ++i) { const CCVector3* P = cloud->getPoint(i); lControlPoints[i] = FbxVector4(P->x,P->y,P->z); } } ccMesh* asCCMesh = 0; if (mesh->isA(CC_MESH)) asCCMesh = static_cast<ccMesh*>(mesh); // normals if (mesh->hasNormals()) { FbxGeometryElementNormal* lGeometryElementNormal = lMesh->CreateElementNormal(); if (mesh->hasTriNormals()) { // We want to have one normal per vertex of each polygon, // so we set the mapping mode to eByPolygonVertex. lGeometryElementNormal->SetMappingMode(FbxGeometryElement::eByPolygonVertex); lGeometryElementNormal->SetReferenceMode(FbxGeometryElement::eIndexToDirect); lGeometryElementNormal->GetIndexArray().SetCount(faceCount*3); if (asCCMesh) { NormsIndexesTableType* triNorms = asCCMesh->getTriNormsTable(); assert(triNorms); for (unsigned i=0; i<triNorms->currentSize(); ++i) { const PointCoordinateType* N = ccNormalVectors::GetNormal(triNorms->getValue(i)); FbxVector4 Nfbx(N[0],N[1],N[2]); lGeometryElementNormal->GetDirectArray().Add(Nfbx); } for (unsigned j=0; j<faceCount; ++j) { int i1,i2,i3; asCCMesh->getTriangleNormalIndexes(j,i1,i2,i3); lGeometryElementNormal->GetIndexArray().SetAt(static_cast<int>(j)*3+0, i1); lGeometryElementNormal->GetIndexArray().SetAt(static_cast<int>(j)*3+1, i2); lGeometryElementNormal->GetIndexArray().SetAt(static_cast<int>(j)*3+2, i3); } } else { for (unsigned j=0; j<faceCount; ++j) { //we can't use the 'NormsIndexesTable' so we save all the normals of all the vertices CCVector3 Na,Nb,Nc; lGeometryElementNormal->GetDirectArray().Add(FbxVector4(Na.x,Na.y,Na.z)); lGeometryElementNormal->GetDirectArray().Add(FbxVector4(Nb.x,Nb.y,Nb.z)); lGeometryElementNormal->GetDirectArray().Add(FbxVector4(Nc.x,Nc.y,Nc.z)); mesh->getTriangleNormals(j,Na,Nb,Nc); lGeometryElementNormal->GetIndexArray().SetAt(static_cast<int>(j)*3+0, static_cast<int>(j)*3+0); lGeometryElementNormal->GetIndexArray().SetAt(static_cast<int>(j)*3+1, static_cast<int>(j)*3+1); lGeometryElementNormal->GetIndexArray().SetAt(static_cast<int>(j)*3+2, static_cast<int>(j)*3+2); } } } else { // We want to have one normal for each vertex (or control point), // so we set the mapping mode to eByControlPoint. lGeometryElementNormal->SetMappingMode(FbxGeometryElement::eByControlPoint); // The first method is to set the actual normal value // for every control point. lGeometryElementNormal->SetReferenceMode(FbxGeometryElement::eDirect); for (unsigned i=0; i<vertCount; ++i) { const PointCoordinateType* N = cloud->getPointNormal(i); FbxVector4 Nfbx(N[0],N[1],N[2]); lGeometryElementNormal->GetDirectArray().Add(Nfbx); } } } else { ccLog::Warning("[FBX] Mesh has no normal! You can manually compute them (select it then call \"Edit > Normals > Compute\")"); } // colors if (cloud->hasColors()) { FbxGeometryElementVertexColor* lGeometryElementVertexColor = lMesh->CreateElementVertexColor(); lGeometryElementVertexColor->SetMappingMode(FbxGeometryElement::eByControlPoint); lGeometryElementVertexColor->SetReferenceMode(FbxGeometryElement::eDirect); for (unsigned i=0; i<vertCount; ++i) { const colorType* C = cloud->getPointColor(i); FbxColor col( FbxDouble3( static_cast<double>(C[0])/MAX_COLOR_COMP, static_cast<double>(C[1])/MAX_COLOR_COMP, static_cast<double>(C[2])/MAX_COLOR_COMP ) ); lGeometryElementVertexColor->GetDirectArray().Add(col); } } // Set material mapping. //FbxGeometryElementMaterial* lMaterialElement = lMesh->CreateElementMaterial(); //lMaterialElement->SetMappingMode(FbxGeometryElement::eByPolygon); //lMaterialElement->SetReferenceMode(FbxGeometryElement::eIndexToDirect); // Create polygons. Assign material indices. { for (unsigned j=0; j<faceCount; ++j) { const CCLib::TriangleSummitsIndexes* tsi = mesh->getTriangleIndexes(j); lMesh->BeginPolygon(static_cast<int>(j)); lMesh->AddPolygon(tsi->i1); lMesh->AddPolygon(tsi->i2); lMesh->AddPolygon(tsi->i3); lMesh->EndPolygon(); } } FbxNode* lNode = FbxNode::Create(pScene,qPrintable(mesh->getName())); lNode->SetNodeAttribute(lMesh); //CreateMaterials(pScene, lMesh); return lNode; }
CC_FILE_ERROR ObjFilter::saveToFile(ccHObject* entity, QString filename, SaveParameters& parameters) { if (!entity) return CC_FERR_BAD_ARGUMENT; if (!entity->isKindOf(CC_TYPES::MESH)) { ccLog::Warning("[OBJ] This filter can only save one mesh (optionally with sub-meshes) at a time!"); return CC_FERR_BAD_ENTITY_TYPE; } //mesh ccGenericMesh* mesh = ccHObjectCaster::ToGenericMesh(entity); if (!mesh || mesh->size() == 0) { ccLog::Warning("[OBJ] Input mesh is empty!"); return CC_FERR_NO_SAVE; } //vertices ccGenericPointCloud* vertices = mesh->getAssociatedCloud(); if (!vertices || vertices->size() == 0) { ccLog::Warning("[OBJ] Input mesh has no vertices?!"); return CC_FERR_NO_SAVE; } unsigned nbPoints = vertices->size(); //try to open file for saving QFile file(filename); if (!file.open(QFile::Text | QFile::WriteOnly)) return CC_FERR_WRITING; //progress ccProgressDialog pdlg(true); unsigned numberOfTriangles = mesh->size(); CCLib::NormalizedProgress nprogress(&pdlg,numberOfTriangles); pdlg.setMethodTitle(qPrintable(QString("Saving mesh [%1]").arg(mesh->getName()))); pdlg.setInfo(qPrintable(QString("Triangles: %1").arg(numberOfTriangles))); pdlg.start(); QTextStream stream(&file); stream.setRealNumberPrecision(sizeof(PointCoordinateType) == 4 ? 8 : 12); stream << "#OBJ Generated by CloudCompare (TELECOM PARISTECH/EDF R&D)" << endl; if (file.error() != QFile::NoError) return CC_FERR_WRITING; for (unsigned i=0; i<nbPoints; ++i) { const CCVector3* P = vertices->getPoint(i); CCVector3d Pglobal = vertices->toGlobal3d<PointCoordinateType>(*P); stream << "v " << Pglobal.x << " " << Pglobal.y << " " << Pglobal.z << endl; if (file.error() != QFile::NoError) return CC_FERR_WRITING; } //normals bool withTriNormals = mesh->hasTriNormals(); bool withVertNormals = vertices->hasNormals(); bool withNormals = withTriNormals || withVertNormals; if (withNormals) { //per-triangle normals if (withTriNormals) { NormsIndexesTableType* normsTable = mesh->getTriNormsTable(); if (normsTable) { for (unsigned i=0; i<normsTable->currentSize(); ++i) { const CCVector3& normalVec = ccNormalVectors::GetNormal(normsTable->getValue(i)); stream << "vn " << normalVec.x << " " << normalVec.y << " " << normalVec.z << endl; if (file.error() != QFile::NoError) return CC_FERR_WRITING; } } else { assert(false); withTriNormals = false; } } //per-vertices normals else //if (withVertNormals) { for (unsigned i=0; i<nbPoints; ++i) { const CCVector3& normalVec = vertices->getPointNormal(i); stream << "vn " << normalVec.x << " " << normalVec.y << " " << normalVec.z << endl; if (file.error() != QFile::NoError) return CC_FERR_WRITING; } } } //materials const ccMaterialSet* materials = mesh->getMaterialSet(); bool withMaterials = (materials && mesh->hasMaterials()); if (withMaterials) { //save mtl file QStringList errors; QString baseName = QFileInfo(filename).baseName(); if (materials->saveAsMTL(QFileInfo(filename).absolutePath(),baseName,errors)) { stream << "mtllib " << baseName << ".mtl" << endl; if (file.error() != QFile::NoError) return CC_FERR_WRITING; } else { materials = 0; withMaterials = false; } //display potential 'errors' for (int i=0; i<errors.size(); ++i) { ccLog::Warning(QString("[OBJ][Material file writer] ")+errors[i]); } } //save texture coordinates bool withTexCoordinates = withMaterials && mesh->hasPerTriangleTexCoordIndexes(); if (withTexCoordinates) { TextureCoordsContainer* texCoords = mesh->getTexCoordinatesTable(); if (texCoords) { for (unsigned i=0; i<texCoords->currentSize(); ++i) { const float* tc = texCoords->getValue(i); stream << "vt " << tc[0] << " " << tc[1] << endl; if (file.error() != QFile::NoError) return CC_FERR_WRITING; } } else { withTexCoordinates = false; } } ccHObject::Container subMeshes; //look for sub-meshes mesh->filterChildren(subMeshes,false,CC_TYPES::SUB_MESH); //check that the number of facets is the same as the full mesh! { unsigned faceCount = 0; for (ccHObject::Container::const_iterator it = subMeshes.begin(); it != subMeshes.end(); ++it) faceCount += static_cast<ccSubMesh*>(*it)->size(); //if there's no face (i.e. no sub-mesh) or less face than the total mesh, we save the full mesh! if (faceCount < mesh->size()) { subMeshes.clear(); subMeshes.push_back(mesh); } } //mesh or sub-meshes unsigned indexShift = 0; for (ccHObject::Container::const_iterator it = subMeshes.begin(); it != subMeshes.end(); ++it) { ccGenericMesh* st = static_cast<ccGenericMesh*>(*it); stream << "g " << (st->getName().isNull() ? "mesh" : st->getName()) << endl; if (file.error() != QFile::NoError) return CC_FERR_WRITING; unsigned triNum = st->size(); st->placeIteratorAtBegining(); int lastMtlIndex = -1; int t1 = -1, t2 = -1, t3 = -1; for (unsigned i=0; i<triNum; ++i) { if (withMaterials) { int mtlIndex = mesh->getTriangleMtlIndex(indexShift+i); if (mtlIndex != lastMtlIndex) { if (mtlIndex >= 0 && mtlIndex < static_cast<int>(materials->size())) { ccMaterial::CShared mat = materials->at(mtlIndex); stream << "usemtl " << mat->getName() << endl; } else { stream << "usemtl " << endl; } if (file.error() != QFile::NoError) return CC_FERR_WRITING; lastMtlIndex = mtlIndex; } if (withTexCoordinates) { mesh->getTriangleTexCoordinatesIndexes(indexShift+i,t1,t2,t3); if (t1 >= 0) ++t1; if (t2 >= 0) ++t2; if (t3 >= 0) ++t3; } } const CCLib::VerticesIndexes* tsi = st->getNextTriangleVertIndexes(); //for per-triangle normals unsigned i1 = tsi->i1 + 1; unsigned i2 = tsi->i2 + 1; unsigned i3 = tsi->i3 + 1; stream << "f"; if (withNormals) { int n1 = static_cast<int>(i1); int n2 = static_cast<int>(i2); int n3 = static_cast<int>(i3); if (withTriNormals) { st->getTriangleNormalIndexes(i,n1,n2,n3); if (n1 >= 0) ++n1; if (n2 >= 0) ++n2; if (n3 >= 0) ++n3; } if (withTexCoordinates) { stream << " " << i1 << "/" << t1 << "/" << n1; stream << " " << i2 << "/" << t2 << "/" << n2; stream << " " << i3 << "/" << t3 << "/" << n3; } else { stream << " " << i1 << "//" << n1; stream << " " << i2 << "//" << n2; stream << " " << i3 << "//" << n3; } } else { if (withTexCoordinates) { stream << " " << i1 << "/" << t1; stream << " " << i2 << "/" << t2; stream << " " << i3 << "/" << t3; } else { stream << " " << i1; stream << " " << i2; stream << " " << i3; } } stream << endl; if (file.error() != QFile::NoError) return CC_FERR_WRITING; if (!nprogress.oneStep()) //cancel requested return CC_FERR_CANCELED_BY_USER; } stream << "#" << triNum << " faces" << endl; if (file.error() != QFile::NoError) return CC_FERR_WRITING; indexShift += triNum; } return CC_FERR_NO_ERROR; }
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; }
//converts a FBX mesh to a CC mesh static ccMesh* FromFbxMesh(FbxMesh* fbxMesh, FileIOFilter::LoadParameters& parameters) { 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 * ccColor::MAX), static_cast<colorType>(c.mGreen * ccColor::MAX), static_cast<colorType>(c.mBlue * ccColor::MAX) ); } } 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 * ccColor::MAX), static_cast<colorType>(c.mGreen * ccColor::MAX), static_cast<colorType>(c.mBlue * ccColor::MAX) ); } } 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); } 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); vertices->showNormals(true); mesh->showNormals(true); } } //materials ccMaterialSet* materials = 0; { FbxNode* lNode = fbxMesh->GetNode(); int lMaterialCount = lNode ? lNode->GetMaterialCount() : 0; for (int i=0; i<lMaterialCount; i++) { FbxSurfaceMaterial *lBaseMaterial = lNode->GetMaterial(i); bool isLambert = lBaseMaterial->GetClassId().Is(FbxSurfaceLambert::ClassId); bool isPhong = lBaseMaterial->GetClassId().Is(FbxSurfacePhong::ClassId); if (isLambert || isPhong) { ccMaterial::Shared mat(new ccMaterial(lBaseMaterial->GetName())); FbxSurfaceLambert* lLambertMat = static_cast<FbxSurfaceLambert*>(lBaseMaterial); float ambient[4]; float diffuse[4]; float emission[4]; float specular[4]; FbxSurfacePhong* lPhongMat = isPhong ? static_cast<FbxSurfacePhong*>(lBaseMaterial) : 0; for (int k=0; k<3; ++k) { ambient[k] = static_cast<float>(lLambertMat->Ambient.Get()[k]); diffuse[k] = static_cast<float>(lLambertMat->Diffuse.Get()[k]); emission[k] = static_cast<float>(lLambertMat->Emissive.Get()[k]); if (lPhongMat) { specular[k] = static_cast<float>(lPhongMat->Specular.Get()[k]); } } mat->setAmbient(ambient); mat->setDiffuse(diffuse); mat->setEmission(emission); if (isPhong) { mat->setSpecular(specular); assert(lPhongMat); mat->setShininess(static_cast<float>(lPhongMat->Shininess)); } //import associated texture (if any) { int lTextureIndex; FBXSDK_FOR_EACH_TEXTURE(lTextureIndex) { FbxProperty lProperty = lBaseMaterial->FindProperty(FbxLayerElement::sTextureChannelNames[lTextureIndex]); if( lProperty.IsValid() ) { int lTextureCount = lProperty.GetSrcObjectCount<FbxTexture>(); FbxTexture* texture = 0; //we can handle only one texture per material! We'll take the non layered one by default (if any) for (int j = 0; j < lTextureCount; ++j) { //Here we have to check if it's layeredtextures, or just textures: FbxLayeredTexture *lLayeredTexture = lProperty.GetSrcObject<FbxLayeredTexture>(j); if (lLayeredTexture) { //we don't handle layered textures! /*int lNbTextures = lLayeredTexture->GetSrcObjectCount<FbxTexture>(); for (int k=0; k<lNbTextures; ++k) { FbxTexture* lTexture = lLayeredTexture->GetSrcObject<FbxTexture>(k); if(lTexture) { } } //*/ } else { //non-layered texture FbxTexture* lTexture = lProperty.GetSrcObject<FbxTexture>(j); if(lTexture) { //we take the first non layered texture by default texture = lTexture; break; } } } if (texture) { FbxFileTexture *lFileTexture = FbxCast<FbxFileTexture>(texture); if (lFileTexture) { const char* texAbsoluteFilename = lFileTexture->GetFileName(); ccLog::PrintDebug(QString("[FBX] Texture absolue filename: %1").arg(texAbsoluteFilename)); if (texAbsoluteFilename != 0 && texAbsoluteFilename[0] != 0) { if (!mat->loadAndSetTexture(texAbsoluteFilename)) { ccLog::Warning(QString("[FBX] Failed to load texture file: %1").arg(texAbsoluteFilename)); } } } } } } } if (!materials) { materials = new ccMaterialSet("materials"); mesh->addChild(materials); } materials->addMaterial(mat); } else { ccLog::Warning(QString("[FBX] Material '%1' has an unhandled type").arg(lBaseMaterial->GetName())); } }
// Converts a CC mesh to an FBX mesh static FbxNode* ToFbxMesh(ccGenericMesh* mesh, FbxScene* pScene, QString filename, size_t meshIndex) { if (!mesh) return 0; FbxNode* lNode = FbxNode::Create(pScene,qPrintable(mesh->getName())); FbxMesh* lMesh = FbxMesh::Create(pScene, qPrintable(mesh->getName())); lNode->SetNodeAttribute(lMesh); ccGenericPointCloud* cloud = mesh->getAssociatedCloud(); if (!cloud) return 0; unsigned vertCount = cloud->size(); unsigned faceCount = mesh->size(); // Create control points. { lMesh->InitControlPoints(vertCount); FbxVector4* lControlPoints = lMesh->GetControlPoints(); for (unsigned i=0; i<vertCount; ++i) { const CCVector3* P = cloud->getPoint(i); lControlPoints[i] = FbxVector4(P->x,P->y,P->z); //lControlPoints[i] = FbxVector4(P->x,P->z,-P->y); //DGM: see loadFile (Y and Z are inverted) } } ccMesh* asCCMesh = 0; if (mesh->isA(CC_TYPES::MESH)) asCCMesh = static_cast<ccMesh*>(mesh); // normals if (mesh->hasNormals()) { FbxGeometryElementNormal* lGeometryElementNormal = lMesh->CreateElementNormal(); if (mesh->hasTriNormals()) { // We want to have one normal per vertex of each polygon, // so we set the mapping mode to eByPolygonVertex. lGeometryElementNormal->SetMappingMode(FbxGeometryElement::eByPolygonVertex); lGeometryElementNormal->SetReferenceMode(FbxGeometryElement::eIndexToDirect); lGeometryElementNormal->GetIndexArray().SetCount(faceCount*3); if (asCCMesh) { NormsIndexesTableType* triNorms = asCCMesh->getTriNormsTable(); assert(triNorms); for (unsigned i=0; i<triNorms->currentSize(); ++i) { const CCVector3& N = ccNormalVectors::GetNormal(triNorms->getValue(i)); FbxVector4 Nfbx(N.x,N.y,N.z); lGeometryElementNormal->GetDirectArray().Add(Nfbx); } for (unsigned j=0; j<faceCount; ++j) { int i1,i2,i3; asCCMesh->getTriangleNormalIndexes(j,i1,i2,i3); lGeometryElementNormal->GetIndexArray().SetAt(static_cast<int>(j)*3+0, i1); lGeometryElementNormal->GetIndexArray().SetAt(static_cast<int>(j)*3+1, i2); lGeometryElementNormal->GetIndexArray().SetAt(static_cast<int>(j)*3+2, i3); } } else { for (unsigned j=0; j<faceCount; ++j) { //we can't use the 'NormsIndexesTable' so we save all the normals of all the vertices CCVector3 Na,Nb,Nc; lGeometryElementNormal->GetDirectArray().Add(FbxVector4(Na.x,Na.y,Na.z)); lGeometryElementNormal->GetDirectArray().Add(FbxVector4(Nb.x,Nb.y,Nb.z)); lGeometryElementNormal->GetDirectArray().Add(FbxVector4(Nc.x,Nc.y,Nc.z)); mesh->getTriangleNormals(j,Na,Nb,Nc); lGeometryElementNormal->GetIndexArray().SetAt(static_cast<int>(j)*3+0, static_cast<int>(j)*3+0); lGeometryElementNormal->GetIndexArray().SetAt(static_cast<int>(j)*3+1, static_cast<int>(j)*3+1); lGeometryElementNormal->GetIndexArray().SetAt(static_cast<int>(j)*3+2, static_cast<int>(j)*3+2); } } } else { // We want to have one normal for each vertex (or control point), // so we set the mapping mode to eByControlPoint. lGeometryElementNormal->SetMappingMode(FbxGeometryElement::eByControlPoint); // The first method is to set the actual normal value // for every control point. lGeometryElementNormal->SetReferenceMode(FbxGeometryElement::eDirect); for (unsigned i=0; i<vertCount; ++i) { const CCVector3& N = cloud->getPointNormal(i); FbxVector4 Nfbx(N.x,N.y,N.z); lGeometryElementNormal->GetDirectArray().Add(Nfbx); } } } else { ccLog::Warning("[FBX] Mesh has no normal! You can manually compute them (select it then call \"Edit > Normals > Compute\")"); } // Set material mapping. bool hasMaterial = false; if (asCCMesh && asCCMesh->hasMaterials()) { const ccMaterialSet* matSet = asCCMesh->getMaterialSet(); size_t matCount = matSet->size(); //check if we have textures bool hasTextures = asCCMesh->hasTextures(); if (hasTextures) { //check that we actually have materials with textures as well! hasTextures = false; for (size_t i=0; i<matCount; ++i) { ccMaterial::CShared mat = matSet->at(i); if (mat->hasTexture()) { hasTextures = true; break; } } } static const char gDiffuseElementName[] = "DiffuseUV"; // Create UV for Diffuse channel if (hasTextures) { FbxGeometryElementUV* lUVDiffuseElement = lMesh->CreateElementUV(gDiffuseElementName); assert(lUVDiffuseElement != 0); lUVDiffuseElement->SetMappingMode(FbxGeometryElement::eByPolygonVertex); lUVDiffuseElement->SetReferenceMode(FbxGeometryElement::eIndexToDirect); //fill Direct Array const TextureCoordsContainer* texCoords = asCCMesh->getTexCoordinatesTable(); assert(texCoords); if (texCoords) { unsigned count = texCoords->currentSize(); lUVDiffuseElement->GetDirectArray().SetCount(static_cast<int>(count)); for (unsigned i=0; i<count; ++i) { const float* uv = texCoords->getValue(i); lUVDiffuseElement->GetDirectArray().SetAt(i,FbxVector2(uv[0],uv[1])); } } //fill Indexes Array assert(asCCMesh->hasPerTriangleTexCoordIndexes()); if (asCCMesh->hasPerTriangleTexCoordIndexes()) { unsigned triCount = asCCMesh->size(); lUVDiffuseElement->GetIndexArray().SetCount(static_cast<int>(3*triCount)); for (unsigned j=0; j<triCount; ++j) { int t1=0, t2=0, t3=0; asCCMesh->getTriangleTexCoordinatesIndexes(j, t1, t2, t3); lUVDiffuseElement->GetIndexArray().SetAt(j*3+0,t1); lUVDiffuseElement->GetIndexArray().SetAt(j*3+1,t2); lUVDiffuseElement->GetIndexArray().SetAt(j*3+2,t3); } } } //Textures used in this file QMap<QString,QString> texFilenames; //directory to save textures (if any) QFileInfo info(filename); QString textDirName = info.baseName() + QString(".fbm"); QDir baseDir = info.absoluteDir(); QDir texDir = QDir(baseDir.absolutePath() + QString("/") + textDirName); for (size_t i=0; i<matCount; ++i) { ccMaterial::CShared mat = matSet->at(i); FbxSurfacePhong *lMaterial = FbxSurfacePhong::Create(pScene, qPrintable(mat->getName())); const ccColor::Rgbaf& emission = mat->getEmission(); const ccColor::Rgbaf& ambient = mat->getAmbient(); const ccColor::Rgbaf& diffuse = mat->getDiffuseFront(); const ccColor::Rgbaf& specular = mat->getDiffuseFront(); lMaterial->Emissive.Set(FbxDouble3(emission.r,emission.g,emission.b)); lMaterial->Ambient .Set(FbxDouble3( ambient.r, ambient.g, ambient.b)); lMaterial->Diffuse .Set(FbxDouble3( diffuse.r, diffuse.g, diffuse.b)); lMaterial->Specular.Set(FbxDouble3(specular.r,specular.g,specular.b)); lMaterial->Shininess = mat->getShininessFront(); lMaterial->ShadingModel.Set("Phong"); if (hasTextures && mat->hasTexture()) { QString texFilename = mat->getTextureFilename(); //texture has not already been processed if (!texFilenames.contains(texFilename)) { //if necessary, we (try to) create a subfolder to store textures if (!texDir.exists()) { texDir = baseDir; if (texDir.mkdir(textDirName)) { texDir.cd(textDirName); } else { textDirName = QString(); ccLog::Warning("[FBX] Failed to create subfolder '%1' to store texture files (files will be stored next to the .fbx file)"); } } QFileInfo fileInfo(texFilename); QString baseTexName = fileInfo.fileName(); //add extension QString extension = QFileInfo(texFilename).suffix(); if (fileInfo.suffix().isEmpty()) baseTexName += QString(".png"); QString absoluteFilename = texDir.absolutePath() + QString("/") + baseTexName; ccLog::PrintDebug(QString("[FBX] Material '%1' texture: %2").arg(mat->getName()).arg(absoluteFilename)); texFilenames[texFilename] = absoluteFilename; } //mat.texture.save(absoluteFilename); // Set texture properties. FbxFileTexture* lTexture = FbxFileTexture::Create(pScene,"DiffuseTexture"); assert(!texFilenames[texFilename].isEmpty()); lTexture->SetFileName(qPrintable(texFilenames[texFilename])); lTexture->SetTextureUse(FbxTexture::eStandard); lTexture->SetMappingType(FbxTexture::eUV); lTexture->SetMaterialUse(FbxFileTexture::eModelMaterial); lTexture->SetSwapUV(false); lTexture->SetTranslation(0.0, 0.0); lTexture->SetScale(1.0, 1.0); lTexture->SetRotation(0.0, 0.0); lTexture->UVSet.Set(FbxString(gDiffuseElementName)); // Connect texture to the proper UV // don't forget to connect the texture to the corresponding property of the material lMaterial->Diffuse.ConnectSrcObject(lTexture); } int matIndex = lNode->AddMaterial(lMaterial); assert(matIndex == static_cast<int>(i)); } //don't forget to save the texture files { for (QMap<QString,QString>::ConstIterator it = texFilenames.begin(); it != texFilenames.end(); ++it) { const QImage image = ccMaterial::GetTexture(it.key()); image.mirrored().save(it.value()); } texFilenames.clear(); //don't need this anymore! } // Create 'triangle to material index' mapping { FbxGeometryElementMaterial* lMaterialElement = lMesh->CreateElementMaterial(); lMaterialElement->SetMappingMode(FbxGeometryElement::eByPolygon); lMaterialElement->SetReferenceMode(FbxGeometryElement::eIndexToDirect); } hasMaterial = true; } // colors if (cloud->hasColors()) { FbxGeometryElementVertexColor* lGeometryElementVertexColor = lMesh->CreateElementVertexColor(); lGeometryElementVertexColor->SetMappingMode(FbxGeometryElement::eByControlPoint); lGeometryElementVertexColor->SetReferenceMode(FbxGeometryElement::eDirect); lGeometryElementVertexColor->GetDirectArray().SetCount(vertCount); for (unsigned i=0; i<vertCount; ++i) { const colorType* C = cloud->getPointColor(i); FbxColor col( static_cast<double>(C[0])/ccColor::MAX, static_cast<double>(C[1])/ccColor::MAX, static_cast<double>(C[2])/ccColor::MAX ); lGeometryElementVertexColor->GetDirectArray().SetAt(i,col); } if (!hasMaterial) { //it seems that we have to create a fake material in order for the colors to be displayed (in Unity and FBX Review at least)! FbxSurfacePhong *lMaterial = FbxSurfacePhong::Create(pScene, "ColorMaterial"); lMaterial->Emissive.Set(FbxDouble3(0,0,0)); lMaterial->Ambient.Set(FbxDouble3(0,0,0)); lMaterial->Diffuse.Set(FbxDouble3(1,1,1)); lMaterial->Specular.Set(FbxDouble3(0,0,0)); lMaterial->Shininess = 0; lMaterial->ShadingModel.Set("Phong"); FbxGeometryElementMaterial* lMaterialElement = lMesh->CreateElementMaterial(); lMaterialElement->SetMappingMode(FbxGeometryElement::eAllSame); lMaterialElement->SetReferenceMode(FbxGeometryElement::eDirect); lNode->AddMaterial(lMaterial); } } // Create polygons { for (unsigned j=0; j<faceCount; ++j) { const CCLib::TriangleSummitsIndexes* tsi = mesh->getTriangleIndexes(j); int matIndex = hasMaterial ? asCCMesh->getTriangleMtlIndex(j) : -1; lMesh->BeginPolygon(matIndex); lMesh->AddPolygon(tsi->i1); lMesh->AddPolygon(tsi->i2); lMesh->AddPolygon(tsi->i3); lMesh->EndPolygon(); } } return lNode; }
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); } }