static int texCoords_cb(p_ply_argument argument)
{
	TextureCoordsContainer* texCoords = 0;
	ply_get_argument_user_data(argument, (void**)(&texCoords), NULL);
	assert(texCoords);
	if (!texCoords)
		return 1;

	long length, value_index;
	ply_get_argument_property(argument, NULL, &length, &value_index);
	//unsupported/invalid coordinates!
	if (length != 6)
	{
		s_invalidTexCoordinates = true;
		return 1;
	}
	if (value_index < 0 || value_index > 5)
		return 1;

	s_texCoord[value_index] = static_cast<float>(ply_get_argument_value(argument));

	if (((value_index+1) % 2) == 0)
	{
		texCoords->addElement(s_texCoord+value_index-1);
		++s_texCoordCount;

		if ((s_texCoordCount % PROCESS_EVENTS_FREQ) == 0)
			QCoreApplication::processEvents();
	}

	return 1;
}
Exemple #2
0
bool ccPlane::setAsTexture(QImage image)
{
	if (image.isNull())
	{
		ccLog::Warning("[ccPlane::setAsTexture] Invalid texture image!");
		return false;
	}

	//texture coordinates
	TextureCoordsContainer* texCoords = getTexCoordinatesTable();
	if (!texCoords)
	{
		texCoords = new TextureCoordsContainer();
		if (!texCoords->reserve(4))
		{
			//not enough memory
			ccLog::Warning("[ccPlane::setAsTexture] Not enough memory!");
			delete texCoords;
			return false;
		}

		//create default texture coordinates
		float TA[2]={0.0f,0.0f};
		float TB[2]={0.0f,1.0f};
		float TC[2]={1.0f,1.0f};
		float TD[2]={1.0f,0.0f};
		texCoords->addElement(TA);
		texCoords->addElement(TB);
		texCoords->addElement(TC);
		texCoords->addElement(TD);

		setTexCoordinatesTable(texCoords);
	}

	if (!hasPerTriangleTexCoordIndexes())
	{
		if (!reservePerTriangleTexCoordIndexes())
		{
			//not enough memory
			ccLog::Warning("[ccPlane::setAsTexture] Not enough memory!");
			setTexCoordinatesTable(0);
			removePerTriangleMtlIndexes();
			return false;
		}
		
		//set default texture indexes
		addTriangleTexCoordIndexes(0,2,1);
		addTriangleTexCoordIndexes(0,3,2);
	}
	
	if (!hasPerTriangleMtlIndexes())
	{
		if (!reservePerTriangleMtlIndexes())
		{
			//not enough memory
			ccLog::Warning("[ccPlane::setAsTexture] Not enough memory!");
			setTexCoordinatesTable(0);
			removePerTriangleTexCoordIndexes();
			return false;
		}

		//set default material indexes
		addTriangleMtlIndex(0);
		addTriangleMtlIndex(0);
	}

	//set material
	if (!getMaterialSet())
		setMaterialSet(new ccMaterialSet());
	ccMaterialSet* materialSet = const_cast<ccMaterialSet*>(getMaterialSet());
	assert(materialSet);
	//remove old material (if any)
	materialSet->clear();
	//add new material
	{
		ccMaterial material("texture");
		material.texture = image;
		materialSet->addMaterial(material);
		//dirty trick: reset material association so that texture will be refreshed!
		materialSet->associateTo(0);
		if (m_currentDisplay)
			materialSet->associateTo(m_currentDisplay);
	}

	showMaterials(true);

	return true;
}
Exemple #3
0
//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;
}
ccMesh* DistanceMapGenerationTool::ConvertConicalMapToMesh(	const QSharedPointer<Map>& map,
															bool counterclockwise,
															QImage mapTexture/*=QImage()*/)
{
	if (!map)
		return 0;

	unsigned meshVertCount = map->xSteps * map->ySteps;
	unsigned meshFaceCount = (map->xSteps-1) * (map->ySteps-1) * 2;
	ccPointCloud* cloud = new ccPointCloud();
	ccMesh* mesh = new ccMesh(cloud);
	mesh->addChild(cloud);
	if (!cloud->reserve(meshVertCount) || !mesh->reserve(meshFaceCount))
	{
		//not enough memory
		delete mesh;
		return 0;
	}

	//compute projection constant
	double nProj = ConicalProjectN(map->yMin,map->yMax) * map->conicalSpanRatio;
	assert(nProj >= -1.0 && nProj <= 1.0);

	//create vertices
	{
		double cwSign = (counterclockwise ? -1.0 : 1.0);
		for (unsigned j=0; j<map->xSteps; ++j)
		{
			//longitude
			double lon_rad = static_cast<double>(j)/static_cast<double>(map->xSteps) * (2.0*M_PI);

			double theta = nProj * (lon_rad - M_PI); //-Pi shift so that the map is well centered
			double sin_theta = sin(theta);
			double cos_theta = cos(theta);

			for (unsigned i=0; i<map->ySteps; ++i)
			{
				double lat_rad = map->yMin + static_cast<double>(i) * map->yStep;
				double r = ConicalProject(lat_rad, map->yMin, nProj);

				CCVector3 Pxyz( static_cast<PointCoordinateType>(cwSign * r * sin_theta),
								static_cast<PointCoordinateType>(-r * cos_theta),
								0);
				cloud->addPoint(Pxyz);
			}
		}
	}

	//create facets
	{
		for (unsigned j=0; j+1<map->xSteps; ++j)
		{
			for (unsigned i=0; i+1<map->ySteps; ++i)
			{
				unsigned vertA = j*map->ySteps + i;
				unsigned vertB = vertA + map->ySteps;
				unsigned vertC = vertB + 1;
				unsigned vertD = vertA + 1;

				mesh->addTriangle(vertB,vertC,vertD);
				mesh->addTriangle(vertB,vertD,vertA);
			}
		}
	}

	//do we have a texture as well?
	if (true/*!mapTexture.isNull()*/) //we force tex. coordinates and indexes creation!
	{
		//texture coordinates
		TextureCoordsContainer* texCoords = new TextureCoordsContainer();
		if (!texCoords->reserve(meshVertCount))
		{
			//not enough memory to finish the job!
			delete texCoords;
			return mesh;
		}

		//create default texture coordinates
		for (unsigned j=0; j<map->xSteps; ++j)
		{
			float T[2] = { static_cast<float>(j)/static_cast<float>(map->xSteps-1), 0.0f };
			for (unsigned i=0; i<map->ySteps; ++i)
			{
				T[1] = static_cast<float>(i)/static_cast<float>(map->ySteps-1);
				texCoords->addElement(T);
			}
		}

		if (!mesh->reservePerTriangleTexCoordIndexes())
		{
			//not enough memory to finish the job!
			delete texCoords;
			return mesh;
		}
		
		//set texture indexes
		{
			for (unsigned j=0; j+1<map->xSteps; ++j)
			{
				for (unsigned i=0; i+1<map->ySteps; ++i)
				{
					unsigned vertA = j*map->ySteps + i;
					unsigned vertB = vertA + map->ySteps;
					unsigned vertC = vertB + 1;
					unsigned vertD = vertA + 1;

					mesh->addTriangleTexCoordIndexes(vertB,vertC,vertD);
					mesh->addTriangleTexCoordIndexes(vertB,vertD,vertA);
				}
			}
		}
	
		//set material indexes
		if (!mesh->reservePerTriangleMtlIndexes())
		{
			//not enough memory to finish the job!
			delete texCoords;
			mesh->removePerTriangleTexCoordIndexes();
			return mesh;
		}
		for (unsigned i=0; i<meshFaceCount; ++i)
			mesh->addTriangleMtlIndex(0);

		//set material
		{
			ccMaterial::Shared material(new ccMaterial("texture"));
			material->setTexture(mapTexture, QString(), false);

			ccMaterialSet* materialSet = new ccMaterialSet();
			materialSet->addMaterial(material);

			mesh->setMaterialSet(materialSet);
		}

		mesh->setTexCoordinatesTable(texCoords);
		mesh->showMaterials(true);
		mesh->setVisible(true);
	}

	return mesh;
}
ccMesh* DistanceMapGenerationTool::ConvertProfileToMesh(ccPolyline* profile,
														const ccGLMatrix& cloudToSurface,
														bool counterclockwise,
														unsigned angularSteps/*=36*/,
														QImage mapTexture/*=QImage()*/)
{
	if (!profile || angularSteps < 3)
	{
		return 0;
	}

	//profile vertices
	CCLib::GenericIndexedCloudPersist* profileVertices = profile->getAssociatedCloud();
	unsigned profVertCount = profileVertices->size();
	if (profVertCount < 2)
	{
		return 0;
	}

	//profile meta-data
	ProfileMetaData profileDesc;
	if (!GetPoylineMetaData(profile, profileDesc))
	{
		assert(false);
		return 0;
	}

	unsigned char Z = static_cast<unsigned char>(profileDesc.revolDim);
	//we deduce the 2 other ('horizontal') dimensions
	const unsigned char X = (Z < 2 ? Z+1 : 0);
	const unsigned char Y = (X < 2 ? X+1 : 0);

	unsigned meshVertCount = profVertCount * angularSteps;
	unsigned meshFaceCount = (profVertCount-1) * angularSteps * 2;
	ccPointCloud* cloud = new ccPointCloud("vertices");
	ccMesh* mesh = new ccMesh(cloud);
	if (!cloud->reserve(meshVertCount) || !mesh->reserve(meshFaceCount))
	{
		//not enough memory
		delete cloud;
		delete mesh;
		return 0;
	}

	ccGLMatrix surfaceToCloud = cloudToSurface.inverse();

	//create vertices
	{
		double cwSign = (counterclockwise ? -1.0 : 1.0);
		for (unsigned j=0; j<angularSteps; ++j)
		{
			double angle_rad = static_cast<double>(j)/angularSteps * (2*M_PI);

			CCVector3d N(sin(angle_rad) * cwSign,
						 cos(angle_rad),
						 0);

			for (unsigned i=0; i<profVertCount; ++i)
			{
				const CCVector3* P = profileVertices->getPoint(i);
				double radius = static_cast<double>(P->x);

				CCVector3 Pxyz;
				Pxyz.u[X] = static_cast<PointCoordinateType>(radius * N.x);
				Pxyz.u[Y] = static_cast<PointCoordinateType>(radius * N.y);
				Pxyz.u[Z] = P->y + profileDesc.heightShift;

				surfaceToCloud.apply(Pxyz);

				cloud->addPoint(Pxyz);
			}
		}
		mesh->addChild(cloud);
	}

	PointCoordinateType h0 = profileVertices->getPoint(0)->y;
	PointCoordinateType dH = profileVertices->getPoint(profVertCount-1)->y - h0;
	bool invertedHeight = (dH < 0);

	//create facets
	{
		for (unsigned j=0; j<angularSteps; ++j)
		{
			unsigned nextJ = ((j+1) % angularSteps);
			for (unsigned i=0; i+1<profVertCount; ++i)
			{
				unsigned vertA = j*profVertCount+i;
				unsigned vertB = nextJ*profVertCount+i;
				unsigned vertC = vertB+1;
				unsigned vertD = vertA+1;

				if (invertedHeight)
				{
					mesh->addTriangle(vertB,vertC,vertD);
					mesh->addTriangle(vertB,vertD,vertA);
				}
				else
				{
					mesh->addTriangle(vertB,vertD,vertC);
					mesh->addTriangle(vertB,vertA,vertD);
				}
			}
		}
	}

	//do we have a texture as well?
	if (!mapTexture.isNull())
	{
		//texture coordinates
		TextureCoordsContainer* texCoords = new TextureCoordsContainer();
		mesh->addChild(texCoords);
		if (!texCoords->reserve(meshVertCount+profVertCount)) //we add a column for correct wrapping!
		{
			//not enough memory to finish the job!
			return mesh;
		}

		//create default texture coordinates
		for (unsigned j=0; j<=angularSteps; ++j)
		{
			float T[2] = {static_cast<float>(j)/static_cast<float>(angularSteps), 0.0f};
			for (unsigned i=0; i<profVertCount; ++i)
			{
				T[1] = (profileVertices->getPoint(i)->y - h0) / dH;
				if (invertedHeight)
					T[1] = 1.0f - T[1];
				texCoords->addElement(T);
			}
		}

		if (!mesh->reservePerTriangleTexCoordIndexes())
		{
			//not enough memory to finish the job!
			return mesh;
		}
		
		//set texture indexes
		{
			for (unsigned j=0; j<angularSteps; ++j)
			{
				unsigned nextJ = ((j+1)/*% angularSteps*/);
				for (unsigned i=0; i+1<profVertCount; ++i)
				{
					unsigned vertA = j*profVertCount+i;
					unsigned vertB = nextJ*profVertCount+i;
					unsigned vertC = vertB+1;
					unsigned vertD = vertA+1;

					if (invertedHeight)
					{
						mesh->addTriangleTexCoordIndexes(vertB,vertC,vertD);
						mesh->addTriangleTexCoordIndexes(vertB,vertD,vertA);
					}
					else
					{
						mesh->addTriangleTexCoordIndexes(vertB,vertD,vertC);
						mesh->addTriangleTexCoordIndexes(vertB,vertA,vertD);
					}
				}
			}
		}
	
		//set material indexes
		if (!mesh->reservePerTriangleMtlIndexes())
		{
			//not enough memory to finish the job!
			mesh->removeChild(texCoords);
			mesh->removePerTriangleTexCoordIndexes();
			return mesh;
		}
		for (unsigned i=0; i<meshFaceCount; ++i)
		{
			mesh->addTriangleMtlIndex(0);
		}

		//set material
		{
			ccMaterial::Shared material(new ccMaterial("texture"));
			material->setTexture(mapTexture, QString(), false);

			ccMaterialSet* materialSet = new ccMaterialSet();
			materialSet->addMaterial(material);

			mesh->setMaterialSet(materialSet);
		}

		mesh->setTexCoordinatesTable(texCoords);
		mesh->showMaterials(true);
		mesh->setVisible(true);
		cloud->setVisible(false);
	}

	return mesh;
}
Exemple #6
0
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;
}
Exemple #7
0
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;
	}
}
//==================================================loadFile=================================================//
CC_FILE_ERROR PlyFilter::loadFile(QString filename, ccHObject& container, LoadParameters& parameters)
{
	//reset statics!
	s_triCount = 0;//三角面片的个数
	s_unsupportedPolygonType = false;//支持多边形类型
	s_texCoordCount = 0;//纹理坐标个数
	s_invalidTexCoordinates = false;//纹理坐标无效
	s_totalScalarCount = 0;//
	s_IntensityCount = 0;//
	s_ColorCount = 0;//颜色个数
	s_NormalCount = 0;//法向量个数
	s_PointCount = 0;//点的个数
	s_PointDataCorrupted = false;
	s_loadParameters = parameters;
	s_Pshift = CCVector3d(0,0,0);

	/****************/
	/***  Header  ***/
	/****************/

	//open a PLY file for reading
	p_ply ply = ply_open(qPrintable(filename), NULL, 0, NULL);
	if (!ply)
		return CC_FERR_READING;

	//ccLog::PrintDebug(QString("[PLY] Opening file '%1' ...").arg(filename));
	ccLog::PrintDebug(QString("[PLY] 打开文件 '%1' ...").arg(filename));

	if (!ply_read_header(ply))
	{
		ply_close(ply);
		return CC_FERR_WRONG_FILE_TYPE;
	}

	//storage mode: little/big endian
	e_ply_storage_mode storage_mode;
	get_plystorage_mode(ply,&storage_mode);

	/*****************/
	/***  Texture  ***/
	/*****************/
	//eventual texture file declared in the comments (keyword: TEXTUREFILE)
	QString textureFileName;
	//texture coordinates
	TextureCoordsContainer* texCoords = 0;

	/******************/
	/***  Comments  ***/
	/******************/
	{
		const char* lastComment = NULL;

		//display comments
		while ((lastComment = ply_get_next_comment(ply, lastComment)))
		{
			ccLog::Print("[PLY][Comment] %s",lastComment);

			//specific case: TextureFile 'filename.ext'
			if (QString(lastComment).toUpper().startsWith("TEXTUREFILE "))
				textureFileName = QString(lastComment).mid(12).trimmed();
		}
	}

	/*******************************/
	/***  Elements & properties  ***/
	/*******************************/

	//Point-based elements (points, colors, normals, etc.)
	std::vector<plyElement> pointElements;
	//Mesh-based elements (vertices, etc.)
	std::vector<plyElement> meshElements;

	//Point-based element properties (coordinates, color components, etc.)
	std::vector<plyProperty> stdProperties;
	//Mesh-based element properties (vertex indexes, etc.)
	std::vector<plyProperty> listProperties;

	//last read element
	plyElement lastElement;
	lastElement.elem = 0;
	while ((lastElement.elem = ply_get_next_element(ply, lastElement.elem)))
	{
		//we get next element info
		ply_get_element_info(lastElement.elem, &lastElement.elementName, &lastElement.elementInstances);

		if (lastElement.elementInstances == 0)
		{
			ccLog::Warning("[PLY] Element '%s' was ignored as it has 0 instance!",lastElement.elementName);
			continue;
		}

		lastElement.properties.clear();
		lastElement.propertiesCount=0;
		lastElement.isList=false;
		//printf("Element: %s\n",lastElement.elementName);

		//last read property
		plyProperty lastProperty;
		lastProperty.prop = 0;
		lastProperty.elemIndex = 0;

		while ((lastProperty.prop = ply_get_next_property(lastElement.elem,lastProperty.prop)))
		{
			//we get next property info
			ply_get_property_info(lastProperty.prop, &lastProperty.propName, &lastProperty.type, &lastProperty.length_type, &lastProperty.value_type);
			//printf("\tProperty: %s (%s)\n",lastProperty.propName,e_ply_type_names[lastProperty.type]);

			if (lastProperty.type == 16) //PLY_LIST
				lastElement.isList = true;

			lastElement.properties.push_back(lastProperty);
			++lastElement.propertiesCount;
		}

		//if we have a "mesh-like" element
		if (lastElement.isList)
		{
			//we store its properties in 'listProperties'
			for (size_t i=0; i<lastElement.properties.size(); ++i)
			{
				plyProperty& prop = lastElement.properties[i];
				prop.elemIndex = (int)meshElements.size();

				//we only keep track of lists (we can't handle per triangle scalars)
				if (prop.type == 16)
					listProperties.push_back(prop);
				else
				{
					ccLog::Warning("[PLY] Unhandled property: [%s:%s] (%s)",
						lastElement.elementName,
						prop.propName,
						e_ply_type_names[prop.type]);
				}
			}
			meshElements.push_back(lastElement);
		}
		else	//else if we have a "point-like" element
		{
			//we store its properties in 'stdProperties'
			for (size_t i=0; i<lastElement.properties.size(); ++i)
			{
				plyProperty& prop = lastElement.properties[i];
				prop.elemIndex = (int)pointElements.size();
				stdProperties.push_back(prop);
			}
			pointElements.push_back(lastElement);
		}
	}

	//We need some points at least!
	if (pointElements.empty())
	{
		ply_close(ply);
		return CC_FERR_NO_LOAD;
	}

	/**********************/
	/***  Objects info  ***/
	/**********************/
	{
		const char* lastObjInfo = NULL;
		while ((lastObjInfo = ply_get_next_obj_info(ply, lastObjInfo)))
			ccLog::Print("[PLY][Info] %s",lastObjInfo);
	}

	/****************/
	/***  Dialog  ***/
	/****************/

	//properties indexes (0 = unassigned)
	static const unsigned nStdProp = 10;
	int stdPropIndexes[nStdProp] = {0,0,0,0,0,0,0,0,0,0};
	int& xIndex = stdPropIndexes[0];
	int& yIndex = stdPropIndexes[1];
	int& zIndex = stdPropIndexes[2];
	int& nxIndex = stdPropIndexes[3];
	int& nyIndex = stdPropIndexes[4];
	int& nzIndex = stdPropIndexes[5];
	int& rIndex = stdPropIndexes[6];
	int& gIndex = stdPropIndexes[7];
	int& bIndex = stdPropIndexes[8];
	int& iIndex = stdPropIndexes[9];

	std::vector<int> sfPropIndexes;
	//int& sfIndex = stdPropIndexes[10];

	static const unsigned nListProp = 2;
	int listPropIndexes[nListProp] = {0,0};
	int& facesIndex = listPropIndexes[0];
	int& texCoordsIndex = listPropIndexes[1];

	//Combo box items for standard properties (coordinates, color components, etc.)
	QStringList stdPropsText;
	stdPropsText << QString("None");
	{
		for (int i=1; i<=static_cast<int>(stdProperties.size()); ++i)
		{
			plyProperty& pp = stdProperties[i-1];
			QString itemText = QString("%1 - %2 [%3]").arg(pointElements[pp.elemIndex].elementName).arg(pp.propName).arg(e_ply_type_names[pp.type]);
			assert(pp.type!=16); //we don't want any PLY_LIST here
			stdPropsText << itemText;

			QString elementName = QString(pointElements[pp.elemIndex].elementName).toUpper();
			QString propName = QString(pp.propName).toUpper();

			if (nxIndex == 0 && (propName.contains("NX") || (elementName.contains("NORM") && propName.endsWith("X"))))
				nxIndex = i;
			else if (nyIndex == 0 && (propName.contains("NY") || (elementName.contains("NORM") && propName.endsWith("Y"))))
				nyIndex = i;
			else if (nzIndex == 0 && (propName.contains("NZ") || (elementName.contains("NORM") && propName.endsWith("Z"))))
				nzIndex = i;
			else if (rIndex == 0 && (propName.contains("RED") || (elementName.contains("COL") && propName.endsWith("R"))))
				rIndex = i;
			else if (gIndex == 0 && (propName.contains("GREEN") || (elementName.contains("COL") && propName.endsWith("G"))))
				gIndex = i;
			else if (bIndex == 0 && (propName.contains("BLUE") || (elementName.contains("COL") && propName.endsWith("B"))))
				bIndex = i;
			else if (iIndex == 0 && (propName.contains("INTENSITY") || propName.contains("GRAY") || propName.contains("GREY") || (elementName.contains("COL") && propName.endsWith("I"))))
				iIndex = i;
			else if (elementName.contains("VERT") || elementName.contains("POINT"))
			{
				if (propName.contains("SCAL"))
					sfPropIndexes.push_back(i);
				else if (xIndex == 0 && propName.endsWith("X"))
					xIndex = i;
				else if (yIndex == 0 && propName.endsWith("Y"))
					yIndex = i;
				else if (zIndex == 0 && propName.endsWith("Z"))
					zIndex = i;
			}
			else if (propName.contains("SCAL") || propName.contains("VAL"))
				sfPropIndexes.push_back(i);
		}
	}

	//Combo box items for list properties (vertex indexes, etc.)
	QStringList listPropsText;
	{
		listPropsText << QString("None");
		for (int i=0; i<static_cast<int>(listProperties.size()); ++i)
		{
			plyProperty& pp = listProperties[i];
			QString itemText = QString("%0 - %1 [%2]").arg(meshElements[pp.elemIndex].elementName).arg(pp.propName).arg(e_ply_type_names[pp.type]);
			assert(pp.type==16); //we only want PLY_LIST here
			listPropsText << itemText;

			QString elementName = QString(meshElements[pp.elemIndex].elementName).toUpper();
			QString propName = QString(pp.propName).toUpper();

			if (elementName.contains("FACE") || elementName.contains("TRI"))
			{
				if (facesIndex == 0 && propName.contains("IND"))
					facesIndex = i+1;
				if (texCoordsIndex == 0 && propName.contains("COORD"))
					texCoordsIndex = i+1;
			}
		}
	}

	//combo-box max visible items
	int stdPropsCount = stdPropsText.count();
	int listPropsCount = listPropsText.count();

	//we need at least 2 coordinates!
	if (stdPropsCount < 2)
	{
		ccLog::Warning("[PLY] This ply file has less than 2 properties defined! (not even X and Y ;)");
		return CC_FERR_MALFORMED_FILE;
	}
	else if (stdPropsCount < 4 && !parameters.alwaysDisplayLoadDialog)
	{
		//brute force heuristic
		xIndex = 1;
		yIndex = 2;
		zIndex = (stdPropsCount > 3 ? 3 : 0);
		facesIndex = (listPropsCount > 1 ? 1 : 0);
	}
	else
	{
		//we count all assigned properties
		int assignedStdProperties = 0;
		{
			for (unsigned i=0; i<nStdProp; ++i)
				if (stdPropIndexes[i] > 0)
					++assignedStdProperties;
		}

		int assignedListProperties = 0;
		{
			for (unsigned i=0; i<nListProp; ++i)
				if (listPropIndexes[i] > 0)
					++assignedListProperties;
		}

		if (	parameters.alwaysDisplayLoadDialog
			||	stdPropsCount > assignedStdProperties+1		//+1 because of the first item in the combo box ('none')
			||	listPropsCount > assignedListProperties+1 )	//+1 because of the first item in the combo box ('none')
		{
			PlyOpenDlg pod/*(MainWindow::TheInstance())*/;

			pod.plyTypeEdit->setText(e_ply_storage_mode_names[storage_mode]);
			pod.elementsEdit->setText(QString::number(pointElements.size()));
			pod.propertiesEdit->setText(QString::number(listProperties.size()+stdProperties.size()));

			//we fill all combo-boxes with all items
			pod.setDefaultComboItems(stdPropsText);
			pod.setListComboItems(listPropsText);

			//try to restore previous context (if any)
			bool hasAPreviousContext = false;
			if (!pod.restorePreviousContext(hasAPreviousContext))
			{
				if (hasAPreviousContext)
					ccLog::Warning("[PLY] Too many differences with the previous file, we reset the dialog.");
				//Set default/guessed element
				pod.xComboBox->setCurrentIndex(xIndex);
				pod.yComboBox->setCurrentIndex(yIndex);
				pod.zComboBox->setCurrentIndex(zIndex);

				pod.rComboBox->setCurrentIndex(rIndex);
				pod.gComboBox->setCurrentIndex(gIndex);
				pod.bComboBox->setCurrentIndex(bIndex);

				pod.iComboBox->setCurrentIndex(iIndex);

				pod.sfComboBox->setCurrentIndex(sfPropIndexes.empty() ? 0 : sfPropIndexes.front());
				for (size_t j=1; j<sfPropIndexes.size(); ++j)
					pod.addSFComboBox(sfPropIndexes[j]);

				pod.nxComboBox->setCurrentIndex(nxIndex);
				pod.nyComboBox->setCurrentIndex(nyIndex);
				pod.nzComboBox->setCurrentIndex(nzIndex);

				pod.facesComboBox->setCurrentIndex(facesIndex);
				pod.textCoordsComboBox->setCurrentIndex(texCoordsIndex);
			}

			//We show the dialog (or we try to skip it ;)
			if (parameters.alwaysDisplayLoadDialog
				&& !pod.canBeSkipped()
				&& !pod.exec())
			{
				ply_close(ply);
				return CC_FERR_CANCELED_BY_USER;
			}

			//Force events processing (to hide dialog)
			QCoreApplication::processEvents();

			xIndex = pod.xComboBox->currentIndex();
			yIndex = pod.yComboBox->currentIndex();
			zIndex = pod.zComboBox->currentIndex();
			nxIndex = pod.nxComboBox->currentIndex();
			nyIndex = pod.nyComboBox->currentIndex();
			nzIndex = pod.nzComboBox->currentIndex();
			rIndex = pod.rComboBox->currentIndex();
			gIndex = pod.gComboBox->currentIndex();
			bIndex = pod.bComboBox->currentIndex();
			iIndex = pod.iComboBox->currentIndex();
			facesIndex = pod.facesComboBox->currentIndex();
			texCoordsIndex = pod.textCoordsComboBox->currentIndex();

			//get (non null) SF properties
			sfPropIndexes.clear();
			{
				for (size_t j=0; j<pod.m_sfCombos.size(); ++j)
					if (pod.m_sfCombos[j]->currentIndex() > 0)
						sfPropIndexes.push_back(pod.m_sfCombos[j]->currentIndex());
			}
		}
	}

	/*************************/
	/***  Callbacks setup  ***/
	/*************************/

	//Main point cloud
	ccPointCloud* cloud = new ccPointCloud("unnamed - Cloud");

	/* POINTS (X,Y,Z) */

	unsigned numberOfPoints = 0;

	assert(xIndex != yIndex && xIndex != zIndex && yIndex != zIndex);

	//POINTS (X)
	if (xIndex > 0)
	{
		long flags = ELEM_POS_0; //X coordinate
		if (xIndex > yIndex && xIndex > zIndex)
			flags |= ELEM_EOL;

		plyProperty& pp = stdProperties[xIndex-1];
		ply_set_read_cb(ply, pointElements[pp.elemIndex].elementName, pp.propName, vertex_cb, cloud, flags);

		numberOfPoints = pointElements[pp.elemIndex].elementInstances;
	}

	//POINTS (Y)
	if (yIndex > 0)
	{
		long flags = ELEM_POS_1; //Y coordinate
		if (yIndex > xIndex && yIndex > zIndex)
			flags |= ELEM_EOL;

		plyProperty& pp = stdProperties[yIndex-1];
		ply_set_read_cb(ply, pointElements[pp.elemIndex].elementName, pp.propName, vertex_cb, cloud, flags);

		if (numberOfPoints > 0)
		{
			if ((long)numberOfPoints != pointElements[pp.elemIndex].elementInstances)
			{
				ccLog::Warning("[PLY] Bad/uncompatible assignation of point properties!");
				delete cloud;
				ply_close(ply);
				return CC_FERR_BAD_ENTITY_TYPE;
			}
		}
		else numberOfPoints = pointElements[pp.elemIndex].elementInstances;
	}

	//POINTS (Z)
	if (zIndex > 0)
	{
		long flags = ELEM_POS_2; //Z coordinate
		if (zIndex > xIndex && zIndex > yIndex)
			flags |= ELEM_EOL;

		plyProperty& pp = stdProperties[zIndex-1];
		ply_set_read_cb(ply, pointElements[pp.elemIndex].elementName, pp.propName, vertex_cb, cloud, flags);

		if (numberOfPoints > 0)
		{
			if ((long)numberOfPoints != pointElements[pp.elemIndex].elementInstances)
			{
				ccLog::Warning("[PLY] Bad/uncompatible assignation of point properties!");
				delete cloud;
				ply_close(ply);
				return CC_FERR_BAD_ENTITY_TYPE;
			}
		}
		else numberOfPoints = pointElements[pp.elemIndex].elementInstances;
	}

	if (numberOfPoints == 0 || !cloud->reserveThePointsTable(numberOfPoints))
	{
		delete cloud;
		ply_close(ply);
		return CC_FERR_NOT_ENOUGH_MEMORY;
	}

	/* NORMALS (X,Y,Z) */

	unsigned numberOfNormals=0;

	assert(nxIndex == 0 || (nxIndex != nyIndex && nxIndex != nzIndex));
	assert(nyIndex == 0 || (nyIndex != nxIndex && nyIndex != nzIndex));
	assert(nzIndex == 0 || (nzIndex != nxIndex && nzIndex != nyIndex));

	//NORMALS (X)
	if (nxIndex > 0)
	{
		long flags = ELEM_POS_0; //Nx
		if (nxIndex > nyIndex && nxIndex > nzIndex)
			flags |= ELEM_EOL;

		plyProperty& pp = stdProperties[nxIndex-1];
		ply_set_read_cb(ply, pointElements[pp.elemIndex].elementName, pp.propName, normal_cb, cloud, flags);

		numberOfNormals = pointElements[pp.elemIndex].elementInstances;
	}

	//NORMALS (Y)
	if (nyIndex > 0)
	{
		long flags = ELEM_POS_1; //Ny
		if (nyIndex > nxIndex && nyIndex > nzIndex)
			flags |= ELEM_EOL;

		plyProperty& pp = stdProperties[nyIndex-1];
		ply_set_read_cb(ply, pointElements[pp.elemIndex].elementName, pp.propName, normal_cb, cloud, flags);

		numberOfNormals = std::max(numberOfNormals, (unsigned)pointElements[pp.elemIndex].elementInstances);
	}

	//NORMALS (Z)
	if (nzIndex > 0)
	{
		long flags = ELEM_POS_2; //Nz
		if (nzIndex > nxIndex && nzIndex > nyIndex)
			flags |= ELEM_EOL;

		plyProperty& pp = stdProperties[nzIndex-1];
		ply_set_read_cb(ply, pointElements[pp.elemIndex].elementName, pp.propName, normal_cb, cloud, flags);

		numberOfNormals = std::max(numberOfNormals, (unsigned)pointElements[pp.elemIndex].elementInstances);
	}

	//We check that the number of normals corresponds to the number of points
	if (numberOfNormals > 0)
	{
		if (numberOfPoints != numberOfNormals)
		{
			ccLog::Warning("[PLY] The number of normals doesn't match the number of points!");
			delete cloud;
			ply_close(ply);
			return CC_FERR_BAD_ENTITY_TYPE;
		}
		if (!cloud->reserveTheNormsTable())
		{
			delete cloud;
			ply_close(ply);
			return CC_FERR_NOT_ENOUGH_MEMORY;
		}
		cloud->showNormals(true);
	}

	/* COLORS (R,G,B) */

	unsigned numberOfColors=0;

	assert(rIndex == 0 || (rIndex != gIndex && rIndex != bIndex));
	assert(gIndex == 0 || (gIndex != rIndex && gIndex != bIndex));
	assert(bIndex == 0 || (bIndex != rIndex && bIndex != gIndex));

	if (rIndex > 0)
	{
		long flags = ELEM_POS_0; //R
		if (rIndex > gIndex && rIndex > bIndex)
			flags |= ELEM_EOL;

		plyProperty& pp = stdProperties[rIndex-1];
		ply_set_read_cb(ply, pointElements[pp.elemIndex].elementName, pp.propName, rgb_cb, cloud, flags);

		numberOfColors = pointElements[pp.elemIndex].elementInstances;
	}

	if (gIndex > 0)
	{
		long flags = ELEM_POS_1; //G
		if (gIndex > rIndex && gIndex > bIndex)
			flags |= ELEM_EOL;

		plyProperty& pp = stdProperties[gIndex-1];
		ply_set_read_cb(ply, pointElements[pp.elemIndex].elementName, pp.propName, rgb_cb, cloud, flags);

		numberOfColors = std::max(numberOfColors, (unsigned)pointElements[pp.elemIndex].elementInstances);
	}

	if (bIndex > 0)
	{
		long flags = ELEM_POS_2; //B
		if (bIndex > rIndex && bIndex > gIndex)
			flags |= ELEM_EOL;

		plyProperty& pp = stdProperties[bIndex-1];
		ply_set_read_cb(ply, pointElements[pp.elemIndex].elementName, pp.propName, rgb_cb, cloud, flags);

		numberOfColors = std::max(numberOfColors, (unsigned)pointElements[pp.elemIndex].elementInstances);
	}

	/* Intensity (I) */

	//INTENSITE (G)
	if (iIndex > 0)
	{
		if (numberOfColors > 0)
		{
			ccLog::Error("Can't import colors AND intensity (intensities will be ignored)!");
			ccLog::Warning("[PLY] intensities will be ignored");
		}
		else
		{
			plyProperty pp = stdProperties[iIndex-1];
			ply_set_read_cb(ply, pointElements[pp.elemIndex].elementName, pp.propName, grey_cb, cloud, 0);

			numberOfColors = pointElements[pp.elemIndex].elementInstances;
		}
	}

	//We check that the number of colors corresponds to the number of points
	if (numberOfColors > 0)
	{
		if (numberOfPoints != numberOfColors)
		{
			ccLog::Warning("The number of colors doesn't match the number of points!");
			delete cloud;
			ply_close(ply);
			return CC_FERR_BAD_ENTITY_TYPE;
		}
		if (!cloud->reserveTheRGBTable())
		{
			delete cloud;
			ply_close(ply);
			return CC_FERR_NOT_ENOUGH_MEMORY;
		}
		cloud->showColors(true);
	}

	/* SCALAR FIELDS (SF) */
	{
		for (size_t i=0; i<sfPropIndexes.size(); ++i)
		{
			int sfIndex = sfPropIndexes[i];
			plyProperty& pp = stdProperties[sfIndex-1];
			
			unsigned numberOfScalars = pointElements[pp.elemIndex].elementInstances;

			//does the number of scalars matches the number of points?
			if (numberOfPoints != numberOfScalars)
			{
				ccLog::Error(QString("Scalar field #%1: the number of scalars doesn't match the number of points (they will be ignored)!").arg(i+1));
				ccLog::Warning(QString("[PLY] Scalar field #%1 ignored!").arg(i+1));
				numberOfScalars = 0;
			}
			else 
			{
				QString qPropName(pp.propName);
				if (qPropName.startsWith("scalar_") && qPropName.length() > 7)
				{
					//remove the 'scalar_' prefix added when saving SF with CC!
					qPropName = qPropName.mid(7).replace('_',' ');
				}

				int sfIdx = cloud->addScalarField(qPrintable(qPropName));
				if (sfIdx >= 0)
				{
					CCLib::ScalarField* sf = cloud->getScalarField(sfIdx);
					assert(sf);
					if (sf->reserve(numberOfScalars))
					{
						ply_set_read_cb(ply, pointElements[pp.elemIndex].elementName, pp.propName, scalar_cb, sf, 1);
					}
					else
					{
						cloud->deleteScalarField(sfIdx);
						sfIdx = -1;
					}
				}
				
				if (sfIdx < 0)
				{
					ccLog::Error(QString("Scalar field #%1: not enough memory to load scalar field (they will be ignored)!").arg(i+1));
					ccLog::Warning(QString("[PLY] Scalar field #%1 ignored!").arg(i+1));
				}
			}
		}
	}

	/* MESH FACETS (TRI) */

	ccMesh* mesh = 0;
	unsigned numberOfFacets = 0;

	if (facesIndex > 0)
	{
		plyProperty& pp = listProperties[facesIndex-1];
		assert(pp.type==16); //we only accept PLY_LIST here!

		mesh = new ccMesh(cloud);

		numberOfFacets = meshElements[pp.elemIndex].elementInstances;

		if (!mesh->reserve(numberOfFacets))
		{
			ccLog::Error("Not enough memory to load facets (they will be ignored)!");
			ccLog::Warning("[PLY] Mesh ignored!");
			delete mesh;
			mesh = 0;
			numberOfFacets = 0;
		}
		else
		{
			ply_set_read_cb(ply, meshElements[pp.elemIndex].elementName, pp.propName, face_cb, mesh, 0);
		}
	}

	if (texCoordsIndex > 0)
	{
		plyProperty& pp = listProperties[texCoordsIndex-1];
		assert(pp.type == 16); //we only accept PLY_LIST here!

		texCoords = new TextureCoordsContainer();
		texCoords->link();

		long numberOfCoordinates = meshElements[pp.elemIndex].elementInstances;
		assert(numberOfCoordinates == numberOfFacets);

		if (!texCoords->reserve(numberOfCoordinates*3))
		{
			ccLog::Error("Not enough memory to load texture coordinates (they will be ignored)!");
			ccLog::Warning("[PLY] Texture coordinates ignored!");
			texCoords->release();
			texCoords = 0;
		}
		else
		{
			ply_set_read_cb(ply, meshElements[pp.elemIndex].elementName, pp.propName, texCoords_cb, texCoords, 0);
		}
	}

	ccProgressDialog pDlg(false);
	if (parameters.alwaysDisplayLoadDialog)
	{
		//pDlg.setInfo("Loading in progress...");
		//pDlg.setMethodTitle("PLY file");
		pDlg.setInfo("正在加载...");
		pDlg.setMethodTitle("PLY 文件");
		pDlg.setRange(0,0);
		pDlg.show();
		QApplication::processEvents();
	}

	//let 'Rply' do the job;)
	int success = 0;
	try
	{
		success = ply_read(ply);
	}
	catch(...)
	{
		success = -1;
	}

	ply_close(ply);

	if (success < 1)
	{
		if (mesh)
			delete mesh;
		delete cloud;
		return CC_FERR_READING;
	}

	//we check mesh
	if (mesh && mesh->size() == 0)
	{
		if (s_unsupportedPolygonType)
			ccLog::Error("Mesh is not triangular! (unsupported)");
		else
			ccLog::Error("Mesh is empty!");
		delete mesh;
		mesh=0;
	}

	if (texCoords && (s_invalidTexCoordinates || s_texCoordCount != 3*mesh->size()))
	{
		ccLog::Error("Invalid texture coordinates! (they will be ignored)");
		texCoords->release();
		texCoords=0;
	}

	//we save parameters
	parameters = s_loadParameters;

	//we update scalar field(s)
	{
		for (unsigned i=0; i<cloud->getNumberOfScalarFields(); ++i)
		{
			CCLib::ScalarField* sf = cloud->getScalarField(i);
			assert(sf);
			sf->computeMinAndMax();
			if (i == 0)
			{
				cloud->setCurrentDisplayedScalarField(0);
				cloud->showSF(true);
			}
		}
	}

	if (mesh)
	{
		assert(s_triCount > 0);
		//check number of loaded facets against 'theoretical' number
		if (s_triCount<numberOfFacets)
		{
			mesh->resize(s_triCount);
			ccLog::Warning("[PLY] Missing vertex indexes!");
		}

		//check that vertex indices start at 0
		unsigned minVertIndex=numberOfPoints,maxVertIndex=0;
		for (unsigned i=0;i<s_triCount;++i)
		{
			const CCLib::TriangleSummitsIndexes* tri = mesh->getTriangleIndexes(i);
			if (tri->i1 < minVertIndex)
				minVertIndex = tri->i1;
			else if (tri->i1 > maxVertIndex)
				maxVertIndex = tri->i1;
			if (tri->i2 < minVertIndex)
				minVertIndex = tri->i2;
			else if (tri->i2 > maxVertIndex)
				maxVertIndex = tri->i2;
			if (tri->i3 < minVertIndex)
				minVertIndex = tri->i3;
			else if (tri->i3 > maxVertIndex)
				maxVertIndex = tri->i3;
		}

		if (maxVertIndex>=numberOfPoints)
		{
			if (maxVertIndex == numberOfPoints && minVertIndex > 0)
			{
				ccLog::Warning("[PLY] Vertex indices seem to be shifted (+1)! We will try to 'unshift' indices (otherwise file is corrupted...)");
				for (unsigned i=0;i<s_triCount;++i)
				{
					CCLib::TriangleSummitsIndexes* tri = mesh->getTriangleIndexes(i);
					--tri->i1;
					--tri->i2;
					--tri->i3;
				}
			}
			else //file is definitely corrupted!
			{
				ccLog::Warning("[PLY] Invalid vertex indices!");
				delete mesh;
				delete cloud;
				return CC_FERR_MALFORMED_FILE;
			}
		}

		mesh->addChild(cloud);
		cloud->setEnabled(false);
		cloud->setName("Vertices");
		//cloud->setLocked(true); //DGM: no need to lock it as it is only used by one mesh!

		//associated texture
		if (texCoords)
		{
			if (!textureFileName.isEmpty())
			{
				QString textureFilePath = QFileInfo(filename).absolutePath() + QString('/') + textureFileName;
				ccMaterial::Shared material(new ccMaterial(textureFileName));
				if (material->loadAndSetTexture(textureFilePath))
				{
					if (mesh->reservePerTriangleTexCoordIndexes() && mesh->reservePerTriangleMtlIndexes())
					{
						const QImage texture = material->getTexture();
						ccLog::Print(QString("[PLY][Texture] Successfully loaded texture '%1' (%2x%3 pixels)").arg(textureFileName).arg(texture.width()).arg(texture.height()));
						//materials
						ccMaterialSet* materials = new ccMaterialSet("materials");
						material->setDiffuse(ccColor::bright);
						material->setSpecular(ccColor::darker);
						material->setAmbient(ccColor::darker);
						materials->push_back(material);
						mesh->setMaterialSet(materials);
						mesh->setTexCoordinatesTable(texCoords);
						for (unsigned i=0;i<mesh->size();++i)
						{
							mesh->addTriangleMtlIndex(0);
							mesh->addTriangleTexCoordIndexes(i*3,i*3+1,i*3+2);
						}
						mesh->showMaterials(true);
					}
					else
					{
						ccLog::Warning("[PLY][Texture] Failed to reserve per-triangle texture coordinates! (not enough memory?)");
					}
				}
				else
				{
					ccLog::Warning(QString("[PLY][Texture] Failed to load texture '%1'").arg(textureFilePath));
				}
			}
			else
			{
				ccLog::Warning("[PLY][Texture] Texture coordinates loaded without an associated image! (we look for the 'TextureFile' keyword in comments)");
			}
		}

		if (cloud->hasColors())
			mesh->showColors(true);
		if (cloud->hasDisplayedScalarField())
			mesh->showSF(true);
		if (cloud->hasNormals())
			mesh->showNormals(true);
		else
		{
			//DGM: normals can be per-vertex or per-triangle so it's better to let the user do it himself later
			//Moreover it's not always good idea if the user doesn't want normals (especially in ccViewer!)
			//mesh->computeNormals();
			ccLog::Warning("[PLY] Mesh has no normal! You can manually compute them (select it then call \"Edit > Normals > Compute\")");
		}

		if (mesh->hasMaterials())
			mesh->showNormals(false);

		container.addChild(mesh);
	}
	else
	{
		container.addChild(cloud);
	}

	if (texCoords)
	{
		texCoords->release();
		texCoords = 0;
	}

	return CC_FERR_NO_ERROR;
}