/*========================================================================= Program: Visualization Toolkit Module: vtkPLYReader.cxx Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen All rights reserved. See Copyright.txt or http://www.kitware.com/Copyright.htm for details. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the above copyright notice for more information. =========================================================================*/ #include "vtkPLYReader.h" #include "vtkCellArray.h" #include "vtkCellData.h" #include "vtkDataArray.h" #include "vtkFloatArray.h" #include "vtkIncrementalOctreePointLocator.h" #include "vtkInformation.h" #include "vtkInformationVector.h" #include "vtkMathUtilities.h" #include "vtkNew.h" #include "vtkObjectFactory.h" #include "vtkPLY.h" #include "vtkPointData.h" #include "vtkPoints.h" #include "vtkPolyData.h" #include "vtkPolygon.h" #include "vtkSmartPointer.h" #include "vtkStringArray.h" #include "vtkUnsignedCharArray.h" #include #include #include #include #include vtkStandardNewMacro(vtkPLYReader); namespace { /** * Create an extra point in 'data' with the same coordinates and data as * the point at cellPointIndex inside cell. This is to avoid texture artifacts * when you have one point with two different texture values (so the latter * value override the first. This results in a texture discontinuity which results * in artifacts). */ vtkIdType duplicateCellPoint(vtkPolyData* data, vtkCell* cell, int cellPointIndex) { // get the old point id vtkIdList* pointIds = cell->GetPointIds(); vtkIdType pointId = pointIds->GetId(cellPointIndex); // duplicate that point and all associated data vtkPoints* points = data->GetPoints(); double* point = data->GetPoint(pointId); vtkIdType newPointId = points->InsertNextPoint(point); for (int i = 0; i < data->GetPointData()->GetNumberOfArrays(); ++i) { vtkDataArray* a = data->GetPointData()->GetArray(i); a->InsertTuple(newPointId, a->GetTuple(pointId)); } // make cell use the new point pointIds->SetId(cellPointIndex, newPointId); return newPointId; } /** * Set a newPointId at cellPointIndex inside cell. */ void setCellPoint(vtkCell* cell, int cellPointIndex, vtkIdType newPointId) { // get the old point id vtkIdList* pointIds = cell->GetPointIds(); // make cell use the new point pointIds->SetId(cellPointIndex, newPointId); } /** * Compare two points for equality */ bool FuzzyEqual(double* f, double* s, double t) { return vtkMathUtilities::FuzzyCompare(f[0], s[0], t) && vtkMathUtilities::FuzzyCompare(f[1], s[1], t) && vtkMathUtilities::FuzzyCompare(f[2], s[2], t); } } // Construct object with merging set to true. vtkPLYReader::vtkPLYReader() { this->Comments = vtkStringArray::New(); this->ReadFromInputString = false; this->FaceTextureTolerance = 0.000001; this->DuplicatePointsForFaceTexture = true; } vtkPLYReader::~vtkPLYReader() { this->Comments->Delete(); this->Comments = nullptr; } namespace { // required so we don't violate ODR typedef struct _plyVertex { float x[3]; // the usual 3-space position of a vertex float tex[2]; float normal[3]; unsigned char red; unsigned char green; unsigned char blue; unsigned char alpha; } plyVertex; typedef struct _plyFace { unsigned char intensity; // optional face attributes unsigned char red; unsigned char green; unsigned char blue; unsigned char alpha; unsigned char nverts; // number of vertex indices in list int* verts; // vertex index list unsigned char ntexcoord; // number of texcoord in list float* texcoord; // texcoord list } plyFace; } int vtkPLYReader::RequestData(vtkInformation* vtkNotUsed(request), vtkInformationVector** vtkNotUsed(inputVector), vtkInformationVector* outputVector) { // get the info object vtkInformation* outInfo = outputVector->GetInformationObject(0); // get the output vtkPolyData* output = vtkPolyData::SafeDownCast(outInfo->Get(vtkDataObject::DATA_OBJECT())); PlyProperty vertProps[] = { { "x", PLY_FLOAT, PLY_FLOAT, static_cast(offsetof(plyVertex, x)), 0, 0, 0, 0 }, { "y", PLY_FLOAT, PLY_FLOAT, static_cast(offsetof(plyVertex, x) + sizeof(float)), 0, 0, 0, 0 }, { "z", PLY_FLOAT, PLY_FLOAT, static_cast(offsetof(plyVertex, x) + sizeof(float) + sizeof(float)), 0, 0, 0, 0 }, { "u", PLY_FLOAT, PLY_FLOAT, static_cast(offsetof(plyVertex, tex)), 0, 0, 0, 0 }, { "v", PLY_FLOAT, PLY_FLOAT, static_cast(offsetof(plyVertex, tex) + sizeof(float)), 0, 0, 0, 0 }, { "nx", PLY_FLOAT, PLY_FLOAT, static_cast(offsetof(plyVertex, normal)), 0, 0, 0, 0 }, { "ny", PLY_FLOAT, PLY_FLOAT, static_cast(offsetof(plyVertex, normal) + sizeof(float)), 0, 0, 0, 0 }, { "nz", PLY_FLOAT, PLY_FLOAT, static_cast(offsetof(plyVertex, normal) + 2 * sizeof(float)), 0, 0, 0, 0 }, { "red", PLY_UCHAR, PLY_UCHAR, static_cast(offsetof(plyVertex, red)), 0, 0, 0, 0 }, { "green", PLY_UCHAR, PLY_UCHAR, static_cast(offsetof(plyVertex, green)), 0, 0, 0, 0 }, { "blue", PLY_UCHAR, PLY_UCHAR, static_cast(offsetof(plyVertex, blue)), 0, 0, 0, 0 }, { "alpha", PLY_UCHAR, PLY_UCHAR, static_cast(offsetof(plyVertex, alpha)), 0, 0, 0, 0 }, }; PlyProperty faceProps[] = { { "vertex_indices", PLY_INT, PLY_INT, static_cast(offsetof(plyFace, verts)), 1, PLY_UCHAR, PLY_UCHAR, static_cast(offsetof(plyFace, nverts)) }, { "intensity", PLY_UCHAR, PLY_UCHAR, static_cast(offsetof(plyFace, intensity)), 0, 0, 0, 0 }, { "red", PLY_UCHAR, PLY_UCHAR, static_cast(offsetof(plyFace, red)), 0, 0, 0, 0 }, { "green", PLY_UCHAR, PLY_UCHAR, static_cast(offsetof(plyFace, green)), 0, 0, 0, 0 }, { "blue", PLY_UCHAR, PLY_UCHAR, static_cast(offsetof(plyFace, blue)), 0, 0, 0, 0 }, { "alpha", PLY_UCHAR, PLY_UCHAR, static_cast(offsetof(plyFace, alpha)), 0, 0, 0, 0 }, { "texcoord", PLY_FLOAT, PLY_FLOAT, static_cast(offsetof(plyFace, texcoord)), 1, PLY_UCHAR, PLY_UCHAR, static_cast(offsetof(plyFace, ntexcoord)) }, }; // open a PLY file for reading PlyFile* ply; int nelems, numElems, nprops; char **elist, *elemName; if (this->ReadFromInputString) { if (!(ply = vtkPLY::ply_open_for_reading_from_string(this->InputString, &nelems, &elist))) { vtkWarningMacro(<< "Could not open PLY file"); return 0; } } else { if (!(ply = vtkPLY::ply_open_for_reading(this->FileName, &nelems, &elist))) { vtkWarningMacro(<< "Could not open PLY file"); return 0; } } int numberOfComments = 0; char** comments = vtkPLY::ply_get_comments(ply, &numberOfComments); this->Comments->Reset(); for (int i = 0; i < numberOfComments; i++) { this->Comments->InsertNextValue(comments[i]); } // Check to make sure that we can read geometry PlyElement* elem; int index; if ((elem = vtkPLY::find_element(ply, "vertex")) == nullptr || vtkPLY::find_property(elem, "x", &index) == nullptr || vtkPLY::find_property(elem, "y", &index) == nullptr || vtkPLY::find_property(elem, "z", &index) == nullptr) { vtkErrorMacro(<< "Cannot read geometry"); vtkPLY::ply_close(ply); } // Check for optional attribute data. We can handle intensity; and the // triplet red, green, blue. bool intensityAvailable = false; vtkSmartPointer intensity = nullptr; if ((elem = vtkPLY::find_element(ply, "face")) != nullptr && vtkPLY::find_property(elem, "intensity", &index) != nullptr) { intensity = vtkSmartPointer::New(); intensity->SetName("intensity"); intensityAvailable = true; output->GetCellData()->AddArray(intensity); output->GetCellData()->SetActiveScalars("intensity"); } bool rgbCellsAvailable = false; bool rgbCellsHaveAlpha = false; vtkSmartPointer rgbCells = nullptr; if ((elem = vtkPLY::find_element(ply, "face")) != nullptr && vtkPLY::find_property(elem, "red", &index) != nullptr && vtkPLY::find_property(elem, "green", &index) != nullptr && vtkPLY::find_property(elem, "blue", &index) != nullptr) { rgbCellsAvailable = true; rgbCells = vtkSmartPointer::New(); if (vtkPLY::find_property(elem, "alpha", &index) != nullptr) { rgbCells->SetName("RGBA"); rgbCells->SetNumberOfComponents(4); rgbCellsHaveAlpha = true; } else { rgbCells->SetName("RGB"); rgbCells->SetNumberOfComponents(3); } output->GetCellData()->AddArray(rgbCells); output->GetCellData()->SetActiveScalars("RGB"); } bool rgbPointsAvailable = false; bool rgbPointsHaveAlpha = false; vtkSmartPointer rgbPoints = nullptr; if ((elem = vtkPLY::find_element(ply, "vertex")) != nullptr) { if (vtkPLY::find_property(elem, "red", &index) != nullptr && vtkPLY::find_property(elem, "green", &index) != nullptr && vtkPLY::find_property(elem, "blue", &index) != nullptr) { rgbPointsAvailable = true; } else if (vtkPLY::find_property(elem, "diffuse_red", &index) != nullptr && vtkPLY::find_property(elem, "diffuse_green", &index) != nullptr && vtkPLY::find_property(elem, "diffuse_blue", &index) != nullptr) { rgbPointsAvailable = true; vertProps[8].name = "diffuse_red"; vertProps[9].name = "diffuse_green"; vertProps[10].name = "diffuse_blue"; } if (rgbPointsAvailable) { rgbPoints = vtkSmartPointer::New(); if (vtkPLY::find_property(elem, "alpha", &index) != nullptr) { rgbPoints->SetName("RGBA"); rgbPoints->SetNumberOfComponents(4); rgbPointsHaveAlpha = true; } else { rgbPoints->SetName("RGB"); rgbPoints->SetNumberOfComponents(3); } output->GetPointData()->SetScalars(rgbPoints); } } bool normalPointsAvailable = false; vtkSmartPointer normals = nullptr; if ((elem = vtkPLY::find_element(ply, "vertex")) != nullptr && vtkPLY::find_property(elem, "nx", &index) != nullptr && vtkPLY::find_property(elem, "ny", &index) != nullptr && vtkPLY::find_property(elem, "nz", &index) != nullptr) { normals = vtkSmartPointer::New(); normalPointsAvailable = true; normals->SetName("Normals"); normals->SetNumberOfComponents(3); output->GetPointData()->SetNormals(normals); } bool texCoordsPointsAvailable = false; vtkSmartPointer texCoordsPoints = nullptr; if ((elem = vtkPLY::find_element(ply, "vertex")) != nullptr) { if (vtkPLY::find_property(elem, "u", &index) != nullptr && vtkPLY::find_property(elem, "v", &index) != nullptr) { texCoordsPointsAvailable = true; } else if (vtkPLY::find_property(elem, "texture_u", &index) != nullptr && vtkPLY::find_property(elem, "texture_v", &index) != nullptr) { texCoordsPointsAvailable = true; vertProps[3].name = "texture_u"; vertProps[4].name = "texture_v"; } if (texCoordsPointsAvailable) { texCoordsPoints = vtkSmartPointer::New(); texCoordsPoints->SetName("TCoords"); texCoordsPoints->SetNumberOfComponents(2); output->GetPointData()->SetTCoords(texCoordsPoints); } } bool texCoordsFaceAvailable = false; if ((elem = vtkPLY::find_element(ply, "face")) != nullptr && !texCoordsPointsAvailable) { if (vtkPLY::find_property(elem, "texcoord", &index) != nullptr) { texCoordsFaceAvailable = true; texCoordsPoints = vtkSmartPointer::New(); texCoordsPoints->SetName("TCoords"); texCoordsPoints->SetNumberOfComponents(2); output->GetPointData()->SetTCoords(texCoordsPoints); } } // Okay, now we can grab the data int numPts = 0, numPolys = 0; for (int i = 0; i < nelems; i++) { // get the description of the first element */ elemName = elist[i]; vtkPLY::ply_get_element_description(ply, elemName, &numElems, &nprops); // if we're on vertex elements, read them in if (elemName && !strcmp("vertex", elemName)) { // Create a list of points numPts = numElems; vtkPoints* pts = vtkPoints::New(); pts->SetDataTypeToFloat(); pts->SetNumberOfPoints(numPts); // Setup to read the PLY elements vtkPLY::ply_get_property(ply, elemName, &vertProps[0]); vtkPLY::ply_get_property(ply, elemName, &vertProps[1]); vtkPLY::ply_get_property(ply, elemName, &vertProps[2]); if (texCoordsPointsAvailable) { vtkPLY::ply_get_property(ply, elemName, &vertProps[3]); vtkPLY::ply_get_property(ply, elemName, &vertProps[4]); texCoordsPoints->SetNumberOfTuples(numPts); } if (normalPointsAvailable) { vtkPLY::ply_get_property(ply, elemName, &vertProps[5]); vtkPLY::ply_get_property(ply, elemName, &vertProps[6]); vtkPLY::ply_get_property(ply, elemName, &vertProps[7]); normals->SetNumberOfTuples(numPts); } if (rgbPointsAvailable) { vtkPLY::ply_get_property(ply, elemName, &vertProps[8]); vtkPLY::ply_get_property(ply, elemName, &vertProps[9]); vtkPLY::ply_get_property(ply, elemName, &vertProps[10]); if (rgbPointsHaveAlpha) { vtkPLY::ply_get_property(ply, elemName, &vertProps[11]); } rgbPoints->SetNumberOfTuples(numPts); } plyVertex vertex; for (int j = 0; j < numPts; j++) { vtkPLY::ply_get_element(ply, (void*)&vertex); pts->SetPoint(j, vertex.x); if (texCoordsPointsAvailable) { texCoordsPoints->SetTuple2(j, vertex.tex[0], vertex.tex[1]); } if (normalPointsAvailable) { normals->SetTuple3(j, vertex.normal[0], vertex.normal[1], vertex.normal[2]); } if (rgbPointsAvailable) { if (rgbPointsHaveAlpha) { rgbPoints->SetTuple4(j, vertex.red, vertex.green, vertex.blue, vertex.alpha); } else { rgbPoints->SetTuple3(j, vertex.red, vertex.green, vertex.blue); } } } output->SetPoints(pts); pts->Delete(); } // if vertex else if (elemName && !strcmp("face", elemName)) { // texture coordinates vtkNew texCoords; // We store a list of pointIds (that have the same texture coordinates) // at the texture index returned by texLocator std::vector> pointIds; pointIds.resize(output->GetNumberOfPoints()); // Used to detect different texture values at a vertex. vtkNew texLocator; texLocator->SetTolerance(this->FaceTextureTolerance); double bounds[] = { 0.0, 1.0, 0.0, 1.0, 0.0, 0.0 }; texLocator->InitPointInsertion(texCoords, bounds); // Create a polygonal array numPolys = numElems; vtkSmartPointer polys = vtkSmartPointer::New(); polys->AllocateEstimate(numPolys, 3); plyFace face; vtkIdType vtkVerts[256]; // Get the face properties vtkPLY::ply_get_property(ply, elemName, &faceProps[0]); if (intensityAvailable) { vtkPLY::ply_get_property(ply, elemName, &faceProps[1]); intensity->SetNumberOfComponents(1); intensity->SetNumberOfTuples(numPolys); } if (rgbCellsAvailable) { vtkPLY::ply_get_property(ply, elemName, &faceProps[2]); vtkPLY::ply_get_property(ply, elemName, &faceProps[3]); vtkPLY::ply_get_property(ply, elemName, &faceProps[4]); if (rgbCellsHaveAlpha) { vtkPLY::ply_get_property(ply, elemName, &faceProps[5]); } rgbCells->SetNumberOfTuples(numPolys); } if (texCoordsFaceAvailable) { vtkPLY::ply_get_property(ply, elemName, &faceProps[6]); texCoordsPoints->SetNumberOfTuples(numPts); if (this->DuplicatePointsForFaceTexture) { // initialize texture coordinates with invalid value for (int j = 0; j < numPts; ++j) { texCoordsPoints->SetTuple2(j, -1, -1); } } } // grab all the face elements vtkNew cell; for (int j = 0; j < numPolys; j++) { // grab and element from the file vtkPLY::ply_get_element(ply, (void*)&face); for (int k = 0; k < face.nverts; k++) { vtkVerts[k] = face.verts[k]; } free(face.verts); // allocated in vtkPLY::ascii/binary_get_element cell->Initialize(face.nverts, vtkVerts, output->GetPoints()); if (intensityAvailable) { intensity->SetValue(j, face.intensity); } if (rgbCellsAvailable) { if (rgbCellsHaveAlpha) { rgbCells->SetValue(4 * j, face.red); rgbCells->SetValue(4 * j + 1, face.green); rgbCells->SetValue(4 * j + 2, face.blue); rgbCells->SetValue(4 * j + 3, face.alpha); } else { rgbCells->SetValue(3 * j, face.red); rgbCells->SetValue(3 * j + 1, face.green); rgbCells->SetValue(3 * j + 2, face.blue); } } if (texCoordsFaceAvailable) { // Test to know if there is a texcoord for every vertex if (face.nverts == (face.ntexcoord / 2)) { if (this->DuplicatePointsForFaceTexture) { for (int k = 0; k < face.nverts; k++) { // new texture stored at the current face float newTex[] = { face.texcoord[k * 2], face.texcoord[k * 2 + 1] }; // texture stored at vtkVerts[k] point float currentTex[2]; texCoordsPoints->GetTypedTuple(vtkVerts[k], currentTex); double newTex3[] = { newTex[0], newTex[1], 0 }; if (currentTex[0] == -1.0) { // newly seen texture coordinates for vertex texCoordsPoints->SetTuple2(vtkVerts[k], newTex[0], newTex[1]); vtkIdType ti; texLocator->InsertUniquePoint(newTex3, ti); pointIds.resize(std::max(ti + 1, static_cast(pointIds.size()))); pointIds[ti].push_back(vtkVerts[k]); } else { if (!vtkMathUtilities::FuzzyCompare( currentTex[0], newTex[0], this->FaceTextureTolerance) || !vtkMathUtilities::FuzzyCompare( currentTex[1], newTex[1], this->FaceTextureTolerance)) { // different texture coordinate // than stored at point vtkVerts[k] vtkIdType ti; int inserted = texLocator->InsertUniquePoint(newTex3, ti); if (inserted) { // newly seen texture coordinate for vertex // which already has some texture coordinates. vtkIdType dp = duplicateCellPoint(output, cell, k); texCoordsPoints->SetTuple2(dp, newTex[0], newTex[1]); pointIds.resize(std::max(ti + 1, static_cast(pointIds.size()))); pointIds[ti].push_back(dp); } else { size_t sameTexIndex = 0; if (pointIds[ti].size() > 1) { double first[3]; output->GetPoint(vtkVerts[k], first); for (; sameTexIndex < pointIds[ti].size(); ++sameTexIndex) { double second[3]; output->GetPoint(pointIds[ti][sameTexIndex], second); if (FuzzyEqual(first, second, this->FaceTextureTolerance)) { break; } } if (sameTexIndex == pointIds[ti].size()) { // newly seen point for this texture coordinate vtkIdType dp = duplicateCellPoint(output, cell, k); texCoordsPoints->SetTuple2(dp, newTex[0], newTex[1]); pointIds[ti].push_back(dp); } } // texture coordinate already seen before, use the vertex // associated with these texture coordinates vtkIdType vi = pointIds[ti][sameTexIndex]; setCellPoint(cell, k, vi); } } // same texture coordinate, nothing to do. } } } else { // if we don't want point duplication we only need to set // the texture coordinates for (int k = 0; k < face.nverts; k++) { // new texture stored at the current face float newTex[] = { face.texcoord[k * 2], face.texcoord[k * 2 + 1] }; texCoordsPoints->SetTuple2(vtkVerts[k], newTex[0], newTex[1]); } } } else { vtkWarningMacro(<< "Number of texture coordinates " << face.ntexcoord << " different than number of points " << face.nverts); } free(face.texcoord); } polys->InsertNextCell(cell); } output->SetPolys(polys); } free(elist[i]); // allocated by ply_open_for_reading elist[i] = nullptr; } // for all elements of the PLY file free(elist); // allocated by ply_open_for_reading vtkDebugMacro(<< "Read: " << numPts << " points, " << numPolys << " polygons"); // close the PLY file vtkPLY::ply_close(ply); return 1; } int vtkPLYReader::CanReadFile(const char* filename) { FILE* fd = vtksys::SystemTools::Fopen(filename, "rb"); if (!fd) return 0; char line[4] = {}; const char* result = fgets(line, sizeof(line), fd); fclose(fd); return (result && strncmp(result, "ply", 3) == 0); } void vtkPLYReader::PrintSelf(ostream& os, vtkIndent indent) { this->Superclass::PrintSelf(os, indent); os << indent << "Comments:\n"; indent = indent.GetNextIndent(); for (int i = 0; i < this->Comments->GetNumberOfValues(); ++i) { os << indent << this->Comments->GetValue(i) << "\n"; } }