// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen // SPDX-FileCopyrightText: Copyright (c) 2006 Atamai, Inc. // SPDX-License-Identifier: BSD-3-Clause #include "vtkMNIObjectReader.h" #include "vtkArrayDispatch.h" #include "vtkCellArray.h" #include "vtkCellData.h" #include "vtkFloatArray.h" #include "vtkInformation.h" #include "vtkInformationVector.h" #include "vtkIntArray.h" #include "vtkMath.h" #include "vtkObjectFactory.h" #include "vtkPointData.h" #include "vtkPoints.h" #include "vtkPolyData.h" #include "vtkProperty.h" #include "vtkStreamingDemandDrivenPipeline.h" #include "vtkStringScanner.h" #include "vtkUnsignedCharArray.h" #include #include #include #include #include #ifndef VTK_BINARY #define VTK_ASCII 1 #define VTK_BINARY 2 #endif //------------------------------------------------------------------------------ VTK_ABI_NAMESPACE_BEGIN vtkStandardNewMacro(vtkMNIObjectReader); #define VTK_MNIOBJ_LINE_LENGTH 256 //------------------------------------------------------------------------------ vtkMNIObjectReader::vtkMNIObjectReader() { this->SetNumberOfInputPorts(0); this->FileName = nullptr; this->Property = vtkProperty::New(); // Whether file is binary or ASCII this->FileType = VTK_ASCII; // File line number for error reporting (ASCII only) this->LineNumber = 0; // State information for reading files this->InputStream = nullptr; this->LineText = new char[VTK_MNIOBJ_LINE_LENGTH]; this->CharPointer = this->LineText; } //------------------------------------------------------------------------------ vtkMNIObjectReader::~vtkMNIObjectReader() { if (this->Property) { this->Property->Delete(); } delete[] this->FileName; delete[] this->LineText; } //------------------------------------------------------------------------------ void vtkMNIObjectReader::PrintSelf(ostream& os, vtkIndent indent) { this->Superclass::PrintSelf(os, indent); os << indent << "FileName: " << (this->FileName ? this->FileName : "none") << "\n"; os << indent << "Property: " << this->Property << "\n"; if (this->Property) { this->Property->PrintSelf(os, indent.GetNextIndent()); } } //------------------------------------------------------------------------------ int vtkMNIObjectReader::CanReadFile(const char* fname) { // First make sure the file exists. This prevents an empty file // from being created on older compilers. vtksys::SystemTools::Stat_t fs; if (vtksys::SystemTools::Stat(fname, &fs) != 0) { return 0; } // Try to read the first line of the file. int status = 0; vtksys::ifstream infile(fname); if (infile.good()) { int objType = infile.get(); if (infile.good()) { objType = toupper(objType); if (objType == 'P' || objType == 'L' || objType == 'M' || objType == 'F' || objType == 'X' || objType == 'Q' || objType == 'T') { status = 1; } } infile.close(); } return status; } //------------------------------------------------------------------------------ // Internal function to read in a line up to 256 characters and then // skip to the next line in the file. int vtkMNIObjectReader::ReadLine(char* line, unsigned int maxlen) { this->LineNumber++; istream& infile = *this->InputStream; infile.getline(line, maxlen); this->CharPointer = line; if (infile.fail()) { if (infile.eof()) { return 0; } if (infile.gcount() == 255) { // Read 256 chars; ignoring the rest of the line. infile.clear(); infile.ignore(VTK_INT_MAX, '\n'); vtkWarningMacro( "Overlength line (limit is 255) in " << this->FileName << ":" << this->LineNumber); } } return 1; } //------------------------------------------------------------------------------ // Skip all whitespace, reading additional lines if necessary int vtkMNIObjectReader::SkipWhitespace() { if (this->FileType == VTK_BINARY) { return 1; } // Only skip whitespace in ASCII files do { char* cp = this->CharPointer; // Skip leading whitespace while (isspace(*cp)) { cp++; } if (*cp != '\0') { this->CharPointer = cp; return 1; } } while (this->ReadLine(this->LineText, VTK_MNIOBJ_LINE_LENGTH)); return 0; } //------------------------------------------------------------------------------ struct vtkMNIObjectReaderFunctor { int Result; template void operator()(TArray* array, vtkMNIObjectReader* self, vtkIdType n) { using ValueType = typename TArray::ValueType; array->SetNumberOfTuples(n / array->GetNumberOfComponents()); if (self->FileType == VTK_BINARY) { auto* data = array->GetPointer(0); // The .obj files use native machine endianness self->InputStream->read(reinterpret_cast(data), n * array->GetDataTypeSize()); // Switch ABGR to RGBA colors if (std::is_same_v && array->GetNumberOfComponents() == 4) { for (vtkIdType i = 0; i < n; i += 4) { unsigned char abgr[4]; abgr[0] = data[0]; abgr[1] = data[1]; abgr[2] = data[2]; abgr[3] = data[3]; data[0] = abgr[3]; data[1] = abgr[2]; data[2] = abgr[1]; data[3] = abgr[0]; data += 4; } } this->Result = !self->InputStream->fail(); } else { // The rest of the code is for ASCII files for (vtkIdType i = 0; i < n; i++) { if (!self->SkipWhitespace()) { vtkErrorWithObjectMacro( self, "Unexpected end of file " << self->FileName << ":" << self->LineNumber); this->Result = 0; return; } char* cp = self->CharPointer; if (!std::is_same_v) { auto result = vtk::scan_value(std::string_view(cp)); auto val = result ? result->value() : 0; cp = const_cast(result->range().data()); array->SetValue(i, val); } else { auto result = vtk::scan_value(std::string_view(cp)); auto val = result ? result->value() : 0; cp = const_cast(result->range().data()); if (val < 0.0 || val > 1.0) { vtkErrorWithObjectMacro( self, "Color value must be [0..1] " << self->FileName << ":" << self->LineNumber); this->Result = 0; return; } unsigned char color = static_cast(val * 255.0); array->SetValue(i, color); } // If nothing was read, there was a syntax error if (cp == self->CharPointer) { vtkErrorWithObjectMacro( self, "Syntax error " << self->FileName << ":" << self->LineNumber); this->Result = 0; return; } self->CharPointer = cp; } this->Result = 1; } } }; //------------------------------------------------------------------------------ // Read floating-point values into a vtkFloatArray. int vtkMNIObjectReader::ParseValues(vtkDataArray* array, vtkIdType n) { auto aos = array->ToAOSDataArray(); vtkMNIObjectReaderFunctor functor; if (!vtkArrayDispatch::DispatchByArray::Execute( aos, functor, this, n)) { vtkErrorMacro(<< "WriteValues: Array " << array->GetClassName() << " not supported."); return 0; } return functor.Result; } //------------------------------------------------------------------------------ // Read an integer value int vtkMNIObjectReader::ParseIdValue(vtkIdType* value) { if (this->FileType == VTK_BINARY) { int val; this->InputStream->read((char*)(&val), sizeof(int)); *value = val; return !this->InputStream->fail(); } // The rest of the code is for ASCII files if (!this->SkipWhitespace()) { vtkErrorMacro("Unexpected end of file " << this->FileName << ":" << this->LineNumber); return 0; } char* cp = this->CharPointer; auto result = vtk::scan_int(std::string_view(cp)); auto lval = result ? result->value() : 0; cp = const_cast(result->range().data()); if (lval > static_cast(VTK_INT_MAX) || lval < static_cast(VTK_INT_MIN)) { vtkErrorMacro( "Value " << lval << " is too large for int " << this->FileName << ":" << this->LineNumber); return 0; } *value = static_cast(lval); // If no bytes were read, that means there was a syntax error if (cp == this->CharPointer) { vtkErrorMacro("Syntax error " << this->FileName << ":" << this->LineNumber); return 0; } this->CharPointer = cp; return 1; } //------------------------------------------------------------------------------ int vtkMNIObjectReader::ReadProperty(vtkProperty* property) { vtkFloatArray* tmpArray = vtkFloatArray::New(); int status = this->ParseValues(tmpArray, 5); if (status != 0) { property->SetAmbient(tmpArray->GetValue(0)); property->SetDiffuse(tmpArray->GetValue(1)); property->SetSpecular(tmpArray->GetValue(2)); property->SetSpecularPower(tmpArray->GetValue(3)); property->SetOpacity(tmpArray->GetValue(4)); } tmpArray->Delete(); return status; } //------------------------------------------------------------------------------ int vtkMNIObjectReader::ReadLineThickness(vtkProperty* property) { vtkFloatArray* tmpArray = vtkFloatArray::New(); int status = this->ParseValues(tmpArray, 1); if (status != 0) { property->SetLineWidth(tmpArray->GetValue(0)); } tmpArray->Delete(); return status; } //------------------------------------------------------------------------------ int vtkMNIObjectReader::ReadNumberOfPoints(vtkIdType* numPoints) { int status = this->ParseIdValue(numPoints); if (status != 0) { if (*numPoints < 0) { // Don't support "compressed" data yet vtkErrorMacro("Bad number of points -> " << *numPoints << " " << this->FileName << ":" << this->LineNumber); status = 0; } else if (*numPoints > VTK_ID_MAX / 4) { vtkErrorMacro( "Too many points -> " << *numPoints << " " << this->FileName << ":" << this->LineNumber); status = 0; } } return status; } //------------------------------------------------------------------------------ int vtkMNIObjectReader::ReadNumberOfCells(vtkIdType* numCells) { int status = this->ParseIdValue(numCells); if (status != 0) { if (*numCells < 0) { vtkErrorMacro( "Bad number of cells -> " << *numCells << " " << this->FileName << ":" << this->LineNumber); status = 0; } else if (*numCells > VTK_ID_MAX / 4) { vtkErrorMacro( "Too many cells -> " << *numCells << " " << this->FileName << ":" << this->LineNumber); status = 0; } } return status; } //------------------------------------------------------------------------------ int vtkMNIObjectReader::ReadPoints(vtkPolyData* data, vtkIdType numPoints) { vtkPoints* points = vtkPoints::New(); int status = this->ParseValues(points->GetData(), 3 * numPoints); if (status != 0) { data->SetPoints(points); } points->Delete(); return status; } //------------------------------------------------------------------------------ int vtkMNIObjectReader::ReadNormals(vtkPolyData* data, vtkIdType numPoints) { vtkFloatArray* normals = vtkFloatArray::New(); normals->SetNumberOfComponents(3); int status = this->ParseValues(normals, 3 * numPoints); if (status != 0) { data->GetPointData()->SetNormals(normals); } normals->Delete(); return status; } //------------------------------------------------------------------------------ int vtkMNIObjectReader::ReadColors( vtkProperty* property, vtkPolyData* data, vtkIdType numPoints, vtkIdType numCells) { // Find out what kind of coloring is used vtkIdType colorType = 0; if (this->ParseIdValue(&colorType) == 0) { return 0; } // Set the number of colors vtkIdType numColors = 1; if (colorType == 1) { numColors = numCells; } else if (colorType == 2) { numColors = numPoints; } else if (colorType != 0) { vtkErrorMacro("Color number must be 0, 1 or 2 " << this->FileName << ":" << this->LineNumber); return 0; } // Read the colors vtkUnsignedCharArray* colors = vtkUnsignedCharArray::New(); colors->SetName("Colors"); colors->SetNumberOfComponents(4); int status = this->ParseValues(colors, 4 * numColors); if (status != 0) { if (colorType == 0) { data->GetCellData()->SetScalars(nullptr); data->GetPointData()->SetScalars(nullptr); property->SetColor( colors->GetValue(0) / 255.0, colors->GetValue(1) / 255.0, colors->GetValue(2) / 255.0); } else if (colorType == 1) { data->GetPointData()->SetScalars(nullptr); data->GetCellData()->SetScalars(colors); property->SetColor(1.0, 1.0, 1.0); } else if (colorType == 2) { data->GetCellData()->SetScalars(nullptr); data->GetPointData()->SetScalars(colors); property->SetColor(1.0, 1.0, 1.0); } } colors->Delete(); return status; } //------------------------------------------------------------------------------ int vtkMNIObjectReader::ReadCells(vtkPolyData* data, vtkIdType numCells, int cellType) { vtkIntArray* endIndices = vtkIntArray::New(); vtkIntArray* cellIndices = vtkIntArray::New(); vtkCellArray* cellArray = vtkCellArray::New(); // Read the cell end indices int status = this->ParseValues(endIndices, numCells); // Read the cell point indices vtkIdType numIndices = 0; if (status != 0) { if (numCells > 0) { numIndices = endIndices->GetValue(numCells - 1); } status = this->ParseValues(cellIndices, numIndices); } // Create the cell array if (status != 0) { cellArray->AllocateExact(numCells, endIndices->GetValue(numCells - 1)); vtkPoints* points = data->GetPoints(); vtkIdType numPoints = points->GetNumberOfPoints(); vtkIdType lastEndIndex = 0; for (vtkIdType i = 0; i < numCells; i++) { vtkIdType endIndex = endIndices->GetValue(i); numIndices = endIndex - lastEndIndex; cellArray->InsertNextCell(numIndices); // Check that the index values are okay and create the cell for (vtkIdType j = 0; j < numIndices; j++) { vtkIdType idx = cellIndices->GetValue(lastEndIndex + j); if (idx > numPoints) { vtkErrorMacro("Index " << idx << " is greater than the" << " total number of points " << numPoints << " " << this->FileName); return 0; } cellArray->InsertCellPoint(idx); } lastEndIndex = endIndex; } if (cellType == VTK_POLYGON) { data->SetPolys(cellArray); } else if (cellType == VTK_POLY_LINE) { data->SetLines(cellArray); } } endIndices->Delete(); cellIndices->Delete(); cellArray->Delete(); return status; } //------------------------------------------------------------------------------ int vtkMNIObjectReader::ReadPolygonObject(vtkPolyData* output) { // Read the surface property if (this->ReadProperty(this->Property) == 0) { return 0; } // Read the number of points vtkIdType numPoints = 0; if (this->ReadNumberOfPoints(&numPoints) == 0) { return 0; } // Read the points if (this->ReadPoints(output, numPoints) == 0) { return 0; } // Read the normals if (this->ReadNormals(output, numPoints) == 0) { return 0; } // Read the number of items vtkIdType numCells = 0; if (this->ReadNumberOfCells(&numCells) == 0) { return 0; } // Read the colors if (this->ReadColors(this->Property, output, numPoints, numCells) == 0) { return 0; } // Read the cells if (this->ReadCells(output, numCells, VTK_POLYGON) == 0) { return 0; } return 1; } //------------------------------------------------------------------------------ int vtkMNIObjectReader::ReadLineObject(vtkPolyData* output) { // Read the line thickness if (this->ReadLineThickness(this->Property) == 0) { return 0; } // Read the number of points vtkIdType numPoints = 0; if (this->ReadNumberOfPoints(&numPoints) == 0) { return 0; } // Read the points if (this->ReadPoints(output, numPoints) == 0) { return 0; } // Read the number of items vtkIdType numCells = 0; if (this->ReadNumberOfCells(&numCells) == 0) { return 0; } // Read the colors if (this->ReadColors(this->Property, output, numPoints, numCells) == 0) { return 0; } // Read the cells if (this->ReadCells(output, numCells, VTK_POLY_LINE) == 0) { return 0; } return 1; } //------------------------------------------------------------------------------ int vtkMNIObjectReader::ReadFile(vtkPolyData* output) { // Initialize the property to default values vtkProperty* property = vtkProperty::New(); this->Property->DeepCopy(property); property->Delete(); // Check that the file name has been set. if (!this->FileName) { vtkErrorMacro("ReadFile: No file name has been set"); return 0; } // Make sure that the file exists. vtksys::SystemTools::Stat_t fs; if (vtksys::SystemTools::Stat(this->FileName, &fs) != 0) { vtkErrorMacro("ReadFile: Can't open file " << this->FileName); return 0; } // Make sure that the file is readable. vtksys::ifstream infile(this->FileName, ios::in); if (infile.fail()) { vtkErrorMacro("ReadFile: Can't read the file " << this->FileName); return 0; } // Check object type int objType = infile.get(); int fileType = VTK_ASCII; if (infile.fail()) { vtkErrorMacro("ReadFile: I/O error for file " << this->FileName); infile.close(); return 0; } if (islower(objType)) { objType = toupper(objType); fileType = VTK_BINARY; } if (objType != 'P' && objType != 'L' && objType != 'M' && objType != 'F' && objType != 'X' && objType != 'Q' && objType != 'T' && objType != 'V') { vtkErrorMacro("ReadFile: File is not a MNI obj file: " << this->FileName); infile.close(); return 0; } #ifdef _WIN32 // Re-open file as binary (only necessary on Windows) if (fileType == VTK_BINARY) { infile.close(); infile.open(this->FileName, ios::in | ios::binary); infile.get(); } #endif this->InputStream = &infile; this->LineNumber = 0; this->FileType = fileType; int status = 1; if (this->FileType == VTK_ASCII) { // Read the line, include the type char in line text for // use in error reporting this->LineText[0] = objType; status = this->ReadLine(&this->LineText[1], VTK_MNIOBJ_LINE_LENGTH - 1); } if (status != 0) { switch (objType) { case 'P': status = this->ReadPolygonObject(output); break; case 'L': status = this->ReadLineObject(output); break; case 'M': case 'F': case 'X': case 'Q': case 'T': case 'V': { vtkErrorMacro("ReadFile: Reading of obj type \"" << (char)objType << "\" is not supported: " << this->FileName); status = 0; } break; } } if (this->FileType == VTK_BINARY) { if (infile.fail()) { if (infile.eof()) { vtkErrorMacro("Premature end of binary file " << this->FileName); } else { vtkErrorMacro("Error encountered while reading " << this->FileName); } } } this->InputStream = nullptr; infile.close(); return status; } //------------------------------------------------------------------------------ int vtkMNIObjectReader::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())); // all of the data in the first piece. if (outInfo->Get(vtkStreamingDemandDrivenPipeline::UPDATE_PIECE_NUMBER()) > 0) { return 0; } // read the file return this->ReadFile(output); } VTK_ABI_NAMESPACE_END